PDA

View Full Version : Design pattern structure



amg_123
چهارشنبه 10 تیر 1394, 12:33 عصر
سلام و درود و سرود بر شما
اگه میشه لظفا در مورد استفاده از interface یکم توضیح بدین. البته موارد استفاده ای مثل code hint و استفاده معمول ازش رو میدونم ولی خب هر جوری فکر میکنم میبینم نمیشه کدایی که داخل کنترلر لاراول مینویسمو اینترفیسی کنم.

در کل میخوام یه جوانمرد قصاب پیدا شه از پایه همراه با مثال استفاده داخل لاراول رو واسمون توضیح بده.

لطفا مثال های واقعی استفاده کنید.
مثلا من داخل پروژم چنین مدلهایی دارم
Login = ازلاعات لاگین کاربرا
Client = پروفایل کلاینتا
Admin = پروفایل ادمینا
و صد تا مدل دیگه

قاعدتا عملیات CRUD رویه همشون اجرا میشه و یه سری مراحل چک کردنو ...

حالا چجوری من باید از interface داخل اینا استفاده کنم
دو روزه دارم روش فکر میکنم ولی به نتیجه ای نرسیدم.
اگه ممکنه از مثالهای ساده استفاده نشه و مثالها تا جایی که امکان داره به واقعیت نزدیک باشن و جواب های کلیشه ای نباشن.(Laravel use باشن مثالها:افسرده:)
واقعا اگه راهنمایی کنید ازتون ممنون میشم.

djtrex
پنج شنبه 11 تیر 1394, 00:39 صبح
سلام و درود و سرود بر شما
اگه میشه لظفا در مورد استفاده از interface یکم توضیح بدین. البته موارد استفاده ای مثل code hint و استفاده معمول ازش رو میدونم ولی خب هر جوری فکر میکنم میبینم نمیشه کدایی که داخل کنترلر لاراول مینویسمو اینترفیسی کنم.

در کل میخوام یه جوانمرد قصاب پیدا شه از پایه همراه با مثال استفاده داخل لاراول رو واسمون توضیح بده.

لطفا مثال های واقعی استفاده کنید.
مثلا من داخل پروژم چنین مدلهایی دارم
Login = ازلاعات لاگین کاربرا
Client = پروفایل کلاینتا
Admin = پروفایل ادمینا
و صد تا مدل دیگه

قاعدتا عملیات CRUD رویه همشون اجرا میشه و یه سری مراحل چک کردنو ...

حالا چجوری من باید از interface داخل اینا استفاده کنم
دو روزه دارم روش فکر میکنم ولی به نتیجه ای نرسیدم.
اگه ممکنه از مثالهای ساده استفاده نشه و مثالها تا جایی که امکان داره به واقعیت نزدیک باشن و جواب های کلیشه ای نباشن.(Laravel use باشن مثالها:افسرده:)
واقعا اگه راهنمایی کنید ازتون ممنون میشم.

قبل از اینکه به سوالتون جواب بدم یکی از اصل های مهم توی لاراول سادگی هست همه اینا بستگی داره به این که اندازه پروژه تون چقدر هست اگه قراره یه بلاگ ساده باشه یا ... نیاز به پیچیدگی خاصی ندارید میتونید از همون eloquent توی خود controller استفاده کنید و خودتونو درگیر اینها نکنید.

اما در مورد سوالتون چیزی که مطرح کردید مجموعه ای از چند مبحثه:
- Design By Contract
- Repository Pattern
- Dependency Injection
- Php Type Hinting

Design By Contract:
contract یا اینترفیس همونجور که از اسمش پیداست قراردادی هست که شما میذارید برای سرویس/کلاس که میخواید بسازید. این باعث میشه که فرضا برای اس ام اس پنل بتونید چند نوع پیاده سازی داشته باشید اما توی اپ فقط با اینترفیس کار کنید در نتیجه هر موقع خواستید پیاده سازی جدیدی اضافه کنید(مثلا اس ام اس پنل خودتون رو عوض کنید و از یه سرویس جدیدی بگیرید) (عملا باعث Loose Coupling میشه یعنی کلاس های شما وابسته به پیاده سازی شما نیست بلکه فقط با اینترفیس کار میکنه )
همچنین هنگام تست خیلی راحت میتونید بدون اینکه حتی کدی بنویسید با همین اینترفیس اون هارو mock کنید و unit testing رو انجام بدید.

توضیحات من کوتاه بود برای اطلاعات بیشتر:
http://laravel.com/docs/5.1/contracts
https://laracasts.com/series/whats-new-in-laravel-5/episodes/7
https://laracasts.com/series/intermediate-laravel/episodes/5

Repository Pattern:
repository ها عملا لایه دسترسی به دیتا هستند شما میتونید با ساخت یه اینترفیس منطق برنامه تون رو مستقل از قسمت دیتا و مدل ها کنید. در نتیجه برای منطق برنامه شما مهم نیست که شما دارید از eloquent استفاده میکنید یا از یه orm دیگه یا از یه دیتابیس دیگه مثل mongodb یا ... یا اصلا دارید دیتا رو mock میکنید برای تست و هر موقع هم نیاز داشته باشید میتونید خیلی راحت بین اینها تغییر کنید. همچنین به شما این امکان رو میده یه بار کد بنویسید و چند جا استفاده کنید. مثلا در کدهاتون نیاز دارید دوست های یه کاربر رو بگیرید و اینکارو در چند جای مختلف استفاده کردید به جای اینکه چند بار کد گرفتن دوستان و query هارو بزنید یک بار توی repository متد getFriendsOf تعریف می کنید. حالا اگه تغییری بخواید بدید فقط اینجا تغییر میدید.

باز هم توضیحات کوتاه بود برای اطلاعات بیشتر:
http://heera.it/laravel-repository-pattern#.VZRJgROE7PA
https://laracasts.com/search?q=repository&q-where=lessons
https://bosnadev.com/2015/03/07/using-repository-pattern-in-laravel-5/

Dependency Injection
توضیحات کامل اینجا هست:
http://laravel.com/docs/5.1/container
https://laracasts.com/series/laravel-5-fundamentals/episodes/26
https://laracasts.com/series/intermediate-laravel/episodes/6

Type Hinting هم که مربوط به php هست و لاراول (در حقیقت container لاراول) از طریق اونها و PHP Reflection کار injection رو انجام میده.
اطلاعات بیشتر:
http://php.net/manual/en/language.oop5.typehinting.php

-------------
مباحث مثل Repository pattern و Design By Contract و Dependency Injection مختص به لاراول یا php نیست و در زبان ها و فریم ورک های دیگه هم وجود داره. اما دو مبحث design by contract و dependency injection از اصل های کلیدی لاراول هست.

------------
متاسفانه بدلیل اینکه سوالتون خیلی کلی بود نتونستم مثالی بزنم چون خیلی مبحث گنگ (یا گنگ تر!) میشد. لینک های بالا همه توضیحات و مثال های خوبی دارن امیدوارم بسته به نیازتون ازش استفاده کنید.
کتاب (کتابچه!) Laravel: From Apprentice To Artisan نوشته Taylor Otwell (سازنده لاراول) هم خیلی جامع توضیح میده اینارو:
https://leanpub.com/laravel


باز هم اگه سوالی داشتید حتما مطرح کنید:)

amg_123
پنج شنبه 11 تیر 1394, 22:18 عصر
واقعا دستتون درد نکنه.
فقط من یه مشکل دارم که هر جوری فکر میکنم نمیدونم کجاها باید اینترفیس استفاده کنم. اگه امکانش هست یک مثال کوچیک بزنید.
ممنونتون میشم.
این چند روزه خیلی کتاب خوندم ولی بازم استفاده خاصی ازش نفهمیدم چون فقط چیزایه ساده گفتن و اصلا مثال کاربردی نزدن. یه جورایی گنگه موضوع. آخه اینترفیس نوشتنم دوباره کاری میشه مثلا من واسه هر کلاس بیام دوباره متداش رو تعریف کنم آخه به نظرم منطقی نیست خب یه بار مینویسیم اکستندش میکنیم، ببخشید خیلی در مقابل فهمیدن مقاومت میکنم ولی خب خیلی واسم گنگه:گریه:.

djtrex
شنبه 13 تیر 1394, 11:03 صبح
توی کتاب Head First Design Pattern بعد از اینکه در مورد خیلی از دیزاین پترن ها صحبت میکنه توی فصل آخر به طنز میگه اونهایی که تازه در مورد دیزاین پترن ها خوندن میان می پرسن برای Hello World از چه دیزاین پترنی استفاده کنم!
در ادامه هم میگه اگه راه حل ساده تری دارید و داره کار میکنه نیاز نیست پیچیدگی بیهوده اضافه کنید ولی اگه مطمئن هستید (نه بر اساس حدس و گمان) که در آینده تغییرات زیادی دارید و مشکل میخورید اون موقع میتونید دیزاین پترن مناسب خودتونو استفاده کنید.

در مورد اینجا هم این صدق میکنه قرار نیست برای همه کلاساتون یه اینترفیس بسازید و ... اینکار بهش میگن over-engineering و فقط پیچیدگی اضافی توی سیستمون اضافه میکنید. البته همه اینا به اندازه پروژه تون یا دقیق تر بگم به context بستگی داره. اینکه قراره چی کار بکنید. فرضا اگه unit test برای کدهاتون نمی نویسید خیلی از چیزهایی که گفته شد فایده ای براتون نداره فقط پیچیدگی الکی اضافه کردید.

----------------
اما بریم سراغ موضوع. برای design by contract و dependency injection این کدی هست که من چند روز پیش برای پروژه فعلیم نوشتم. برای push notification فرستادن از سمت سرور به موبایل/تبلت ها موقعی که یه آیتم ساخته میشه و مشترکین باید مطلع میشدن یا نظری به یه آیتم داده میشد و صاحب آیتم باید مطلع میشد نیاز به این داشتم از یه سرویسی برای اینکار استفاده کنم. من بعد از گشتم pushbots رو پیدا کردم. اما نکته ای که داشت اینکه کلاسی که داشت ناقص بود و اسم متدها هم به پروژه من نمیخورد در نتیجه براش یه wrapper نوشتم. اما برای اینکار مطمئن بودم احتمال داره در آینده از این سیستم استفاده نکنم و چون در جاهای مختلف از این کلاس استفاده کردم در نتیجه تصمیم گرفتم یه اینترفیس براش تعریف کنم و به جای استفاده مستقیم از کلاس و وابسته شدن به پیاده سازی اون با اینترفیس کار کنم.

اینترفیس ساده ای که نوشتم (که بعدا میتونه تغییر کنه در صورت نیاز!):



<?php
namespace App\Utility\Push\Contract;


interface PushService
{
public function to(array $users);


public function message($message);

public function push();
}


اینم کلاس wrapper من برای کتابخانه pushbots هست:



<?php
namespace App\Utility\Push\Pushbots;


use App\Utility\Push\Contract\PushService;


class PushBots implements PushService
{
private $pushBots;


private $platforms = [
PushBotsLib::PLATFORM_IOS,
PushBotsLib::PLATFORM_ANDROID,
PushBotsLib::PLATFORM_CHROME,
];


function __construct(PushBotsLib $pushBots)
{
$this->pushBots = $pushBots;
}


public function to(array $users)
{
$this->pushBots->Alias($users);


return $this;
}


public function message($message)
{
$this->pushBots->Alert($message);


return $this;
}


public function push()
{
$this->pushBots->Platform($this->platforms);


return $this->pushBots->Push();
}


}


# برای ساده و کوتاه شدن کدها doctype هارو پاک کردم:)

حالا سوال این هست که چه جوری از این کلاس استفاده کنم. اگه توی متدها یا constructor کلاسم بنویسم:



function __construct(PushBots $pushBots)
{
$this->pushBots = $pushBots;
}


container لاراول یه نمونه از PushBots میسازه همچنین چون توی constructor اون هم PushBotsLib رو هم میسازه و پاس میده (به صورت بازگشتی).
اما این یه مشکل داره اولا اینکه از اینتفریس استفاده نکردم دوما اینکه api سرویس pushbots باید app_secret و app_id رو تنظیم کنید تا کار کنه!.

برای اینکار container لاراول متد bind رو گذاشته. با این متد میتونید بگید هر موقع فلان چیز رو خواستم نمونه این کلاس رو پاس بده! یا حتی میتونید یه closure به عنوان آرگومان دوم پاس بدید و بعد از تنظیماتی که انجام دادید (مثلا در اینجا app_secret و app_id تنظیم باید بشن) اون شی رو پاس بدید.



app()->bind(PushService::class, function () {


$pushBotsLib = new PushBotsLib();
$pushBotsLib->App(
config('services.pushbots.app_id'),
config('services.pushbots.app_secret')
);


return new PushBots($pushBotsLib);
});


خوب به نظر همه چی خوب میاد + اینکه هر موقع نیاز به این داشتم که از یه سرویس دیگه استفاده کنم کافیه نمونه یه کلاس دیگه رو پاس بدم! فقط همین!

اما سوال دیگه اینه که اینو کجا باید بذاریم تا کار کنه. راحت ترین کار گذاشتن توی routes.php هست ولی خوب این باعث میشه خیلی شلوغ و بهم ریخته بشه!. برای همین لاراول Service Provider هارو معرفی کرده. همراه با لاراول به صورت پیشفرض AppServiceProvider توی app/Providers وجود داره میتونید از همون استفاده کنید یا اینکه با دستور:



php artisan make:provider PushServiceProvider

یک service provider جدید بسازید. اگه از روش دوم استفاده کردید باید اونو در آرایه providers در config/app.php اضافه کنید تا لاراول اونو لود کنه.

حالا کافیه کد bind مون رو توی متد register بذاریم:



<?php


namespace App\Providers;


use Illuminate\Support\ServiceProvider;
use App\Utility\Push\Contract\PushService;
use App\Utility\Push\Pushbots\PushBots;
use App\Utility\Push\Pushbots\PushBotsLib;


class PushServiceProvider extends ServiceProvider
{


public function boot()
{
//
}


public function register()
{
$this->app->bind(PushService::class, function () {


$pushBotsLib = new PushBotsLib();
$pushBotsLib->App(
config('services.pushbots.app_id'),
config('services.pushbots.app_secret')
);


return new PushBots($pushBotsLib);
});
}
}



برای استفاده هم کافیه توی متد یا constructor کنترل ها یا متدهای handle رویداد ها یا jobs/commands یا و ... استفاده کنم. (به صورت دستی هم میتونید از dependency injection لاراول توی کلاس های دیگه استفاده کنید)



public function handle(PushService $push){

$name = $this->user->fullname;
$text = $this->comment->text;
$username = $this->author->username;


$push->message(trans('push.new_comment', compact('name', 'text')));
$push->to([$username]);
$push->push();
}



--------------
مثال خیلی خوب دیگه این هست که پیشنهاد میکنم حتما ببینید:
https://laracasts.com/series/search-as-a-service/episodes/1

در مورد service provider:
http://laravel.com/docs/5.1/providers

در مورد service container:
http://laravel.com/docs/5.1/container
-------------

چون طولانی شد در مورد repository pattern ننوشتم توی پست بعد در صورت تمایل در مورد اون هم مینویسم:)

------------
متاسفانه این تگ PHP انجمن خیلی مشکل داره تمام کدهامو خراب کرد مجبور شدم با CODE بزارم!

amg_123
چهارشنبه 17 تیر 1394, 11:03 صبح
واقعا ممنون خیلی کامل، به درد بخور و قابل فهم بود.

repository pattern اگه بشه توضیح بدین که عالیه چون من هنوز به این پترنه نرسیدم فعلا هشتاشونو خوندم.:لبخند: