الگوی طراحی Decorator به زبان ساده در لاراول

در این آموزش قصد داریم تا با Decorator Design Pattern در قالب مثالی کاربردی در فریمورک لاراول آشنا شویم. همانطور که میدانیم Decorator یکی از زیرشاخه‌های الگوهای طراحی Structural (ساختاری) است و به‌کارگیری از آن در شرایطی موجب بهبود پروسه ی توسعه ی اپلیکیشن می‌شود که دولوپرها بخواهند تا یکسری فیچر خاص را به کلاس مد نظر خود بیفزایند. بعبارتی دیگر هدف از پیاده سازی الگوی Decorator اضافه کردن یک وضعیت و یا یک رفتار (Behavior) به یک کلاس بدون تغییر دادن آن می باشد. این عمل میتواند کاملا به صورت داینامیک انجام شود. این دو مشخصه (تغییر نکردن کلاس فعلی و داینامیک بودن)، Decorator را تبدیل به یکی از پرکاربردترین الگوهای طراحی شیء گرا کرده است.قبل از اینکه به پیاده سازی مثال برسیم، به این نکته توجه کنیم که خب شاید بشه از ارثبری هم استفاده کرد! ولی به سه دلیل استفاده از این الگو اولویت بالاتری داری:نکته ی اول، با دیدن مثالهای زیر متوجه میشیم که این الگو در نهایت دست ما را خیلی بازتر میگذارد و قابلیت توسعه پذیری بالاتری به ما میدهد، و با اینکه شاید بعضی وقتا برای پیاده سازی، کمی پیچیدگی اضافه کند، ولی در مجموع اگه سر جای خودش استفاده بشود بسیار ساخت یافته تر هست.نکته ی دوم، زمانی که تعداد کلاس کمی ارثبری می کنند شاید بتونیم به یکسری موارد دقت کنیم و سعی کنیم پیچیدگی را کم کنیم، ولی با زیاد شدن کلاسها، این الگو به ما کمک می کند که بتونیم مدیریت بهتری روی آنها داشته باشیم.نکته ی سوم هم اینکه ما با استفاده از ارثبری، در زمان کامپایل تمام ویژگی های کلاس پدر را به فرزندانش و طبیعتاً به همه ی اشیائ اون کلاس ها منتقل میکنیم، در صورتی که با الگوی decorator، میتوانیم در زمان اجرا یا همون runtime، تغییرات دلخواه خود را بر اساس نیازمان، در یک شئ خاص استفاده کنیم.(منبع)طرح مسئلهفرض کنید که ما یک مدل به نام postداریم:class Post Extends Model { public function scopePublished($query) { return $query->where('published_at', '<=', 'NOW()'); } }و در PostController ما متدی مانند زیر را داریم:class PostsController extends Controller { public function index() { $posts = Post::published()->get(); return $posts; } }جهت کش کردن پست ها و جلوگیری از زدن کوئری و اتصال به دیتابیس در هربار فراخوانی لیست پست ها، می توانیم موارد زیر را انجام دهیم:class PostsController extends Controller { public function index() { $minutes = 1440; # 1 day $posts = Cache::remember('posts', $minutes, function () { return Post::published()->get(); }); return $posts; } }اکنون ما پست ها را برای یک روز کش می کنیم. اما با نگاه کردن به کد کنترلر متوجه می شویم که اطلاعات زیادی به کنترلر داده شده است. بطور مثال در کنترلر تعداد روزهایی که ما داده ها را کش می کنیم ، محاسبه و در خود نگه می دارد.همچنین ، تصور کنید که شما دقیقا همین متد کش را برای برچسب ها ، دسته ها ، آرشیو ها که در HomePageController هستند، بخواهید اجرا کنید. که تعداد اتصال به دیتابیس و کوئری زدن خیلی زیاد خواهد بود.الگوی طراحی Repository patternدر بیشتر موارد ، الگوی Repository به الگوی دکوراتور متصل می شود . چگونه؟ در ادامه خواهیم دید.ابتدا بیایید نحوه دریافت پست ها با استفاده از Repository pattern را بررسی کنیم.ابتدا فایل app/Repositories/Posts/PostsRepositoryInterface.php را با کدهای زیر ایجاد می کنیم: namespace App\Repositories\Posts; interface PostsRepositoryInterface { public function get(); public function find(int $id); }در همان آدرس فایل دیگری با نام PostsRepository با محتویات زیر می سازیم:namespace App\Repositories\Posts; use App\Post; class PostsRepository implements PostsRepositoryInterface{ protected $model; public function __construct(Post $model) { $this->model = $model; } public function get() { return $this->model->published()->get(); } public function find(int $id) { return $this->model->published()->find($id); } }حال به PostsController می رویم، و تغییرات زیر را در آن اعمال می کنیم:namespace App\Http\Controllers; use App\Repositories\Posts\PostsRepositoryInterface; use Illuminate\Http\Request; class PostsController extends Controller { public function index(PostsRepositoryInterface $postsRepo) { return $postsRepo->get(); } }الان PostsController ما به شکل قابل قبول درآمد.الان ما به IOC Laravel نیاز داریم تا بتوانیم شیء ساخته شده از اینترفیس Posts را برای دریافت پست های منتشر شده، تزریق کنیم.تنها کاری که ما باید انجام دهیم این است که به IOC Laravel بگوییم که در هنگام استفاده از Interface، کدام کلاس را ایجاد کند. برای اینکار بصورت زیر عمل می کنیم.در فایل app/Providers/AppServiceProvider.php متد bind را اضافه می کنیم. namespace App\Providers; use App\Repositories\Posts\PostsRepositoryInterface; use App\Repositories\Posts\PostsRepository; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register() { $this->app->bind(PostsRepositoryInterface::class,PostsRepository::class); } }اکنون هر زمان که ما PostsRepositoryInterface را فراخوانی کنیم، نمونه ای از PostsRepository ایجاد میشود و دیتاهای لازم را به ما برمیگرداند.اجرای کش کردن داده ها از طریق الگوی طراحی Decorator همانطور که گفتیم، الگوی طراحی Decorator اجازه می دهد یک رفتار(متد) بدون اینکه روی سایر اشیاء از همان کلاس تأثیر بگذارد ،روی یک شی خاص از آن کلاس متد اجرا شود.دراینجا، آن object/class از ریپوزیتوری PostsRepository ساخته می شود و آن رفتار خاص، کش کردن (caching) داده هاست. قبل از هرچیزی ابتدا ریپوزیتوری مربوط به کش کردن اطلاعات پست(PostsCacheRepository) را در دایرکتوری app/Repositories/Posts/PostsCacheRepository.php بصورت زیر ایجاد کنیم:namespace App\Repositories\Posts; use App\Post; use Illuminate\Cache\CacheManager;class PostsCacheRepository implements PostsRepositoryInterface { protected $repo; protected $cache; const TTL = 1440; # 1 day public function __construct(CacheManager $cache, PostsRepository $repo) { $this->repo = $repo; $this->cache = $cache; } public function get() { return $this->cache->remember('posts', self::TTL, function () { return $this->repo->get(); }); } public function find(int $id) { return $this->cache->remember('posts.'.$id, self::TTL, function () { return $this->repo->find($id); }); } }در این کلاس، ابتدا شی ای از کلاس cache و پست PostsRepository دریافت می کنیم، سپس از کلاس (دکوراتور) جهت اعمال رفتار کش کردن بر روی نمونه object ساخته شده از PostsReposiory، استفاده می کنیم.ما می توانیم از همین مثال برای ارسال درخواست های HTTP به برخی سرویس ها استفاده کنیم و در صورت عدم موفقیت و fail شدن دوباره به مدل برگردد. تا اینجا، به مزیت های استفاده از الگوی طراحی پی برده شده است.آخرین قدم تعریف کردن PostsCacheRepository در فایل AppServiceProvider است که بجای PostsRepository از PostsCacheRepository استفاده شود.namespace App\Providers; use App\Repositories\Posts\PostsRepositoryInterface; use App\Repositories\Posts\PostsCacheRepository; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register() { $this->app->bind(PostsRepositoryInterface::class,PostsCacheRepository::class); } }اکنون هرگاه فایل ها مجدد بررسی شود، متوجه می شویم که خواندن و نگهداری آنها بسیار آسان شده است. همچنین، قابلیت تست کردن و reuse و recode کردن آنها بسیار افزایش یافته است.شما فقط باید اتصالات(binding) را در AppServiceProvider تغییر دهید و نیاز به هیچ کار اضافی برای تغییر نیاز وجود ندارد.نتیجه گیریدر این مقاله آموختیم که اطلاعات مربوط به مدل ها را با استفاده از الگوی طراحی Decorator چگونه کش کنیم. چگونگی اتصال الگوی طراحی Repository با Decorator را نشان داده شد.دیدیم که چگونه Dependecy Injection و IOC Laravel زندگی کدنویسی ما را آسان می کند.متوجه شدیم که امکانات لاراول چقدر قدرتمند هستند.امیدوارم که از خواندن مقاله لذت برده باشید. هدف این مقاله این بود که قدرت الگوهای طراحی و چگونگی آسان سازی حفظ و مدیریت پروژه ها را به نمایش بگذارد. بخش مثال های این مقاله از ترجمه ی مقاله ای که در اینجا هست، گرفته شده است.