راضیه صفری
راضیه صفری
خواندن ۱۲ دقیقه·۳ سال پیش

از سیر تا پیاز پترن Strategy توی لاراول

سلام سلااااام .

از اونجایی که کار جدیدم هنوز استارت نخورده و بخاطر درگیری های سال نو و ... اونها یه وقفه ی چند هفته ای خووووب گیرم اومد واسه خودم کتابای نسبتا قدیمیم رو ریختم بیرون که یه مروری کنم و یسری چیزاشو که برام جذاب بود کنار هم بذارم و بیام اینجاااااا و بنویسم. شاید که واسه کسای دیگه هم جذاب باشه . خب میتونستم کتابا و مطالبای جدیدتر بخونم . اما یهو این تصمیمو گرفتم که چیزای گذشته رو دوباره مرور کنم . شاید بگین چرا خببب. راضیه میگه : چونکه چه توی مباحث مربوط به کار چه هر زمینه ی دیگه ای ، وقتی برای بار چندم یه کتاب و مطلب رو میخونی با توجه به دید بازتر و کلا درک جدیدتری که نسبت به گذشتت توی اون زمینه پیدا کردی ، بازمممم و بازمممم چیزای جدید از اون مطلب و کتاب یاد میگیری و هربار عمیق تر و عمیق تر. حالا چی اوردمممم براتون؟؟؟؟؟ از عنوان پست م مشخصه :دی

از دوتا کتاب : Dive into design patterns که سایت معروف https://refactoring.guru هم معرفیش کرده و یه تیکه هاییش رو گذاشته و کتاب

Laravel Design Patterns And Best Practices

قبل از اینکه برید ادامه مطلب یه نکته بگم ببینید اصن حال میکنید برید ادامشو بخونید یا نه؟ اینکه چرا این پترن رو اصلا دارم توی لاراول بررسی میکنم ؟ مگه اینهمممه مثال ازش توی نت نیست ؟

چرا چرااا هست . اما کدوم مثال بهتر از چیزی که توی یه فریم وورک خفن یسری دولوپر خفن اومدن و به بهترین شکل پیادش کردن ؟ بنظر منکه همیشه بررسی و نگاه کردن دل و روده ی فریم وورک های حرفه ای و معروف یا یسری کدهای حرفه ای اوپن سورس معروف ، "یکی از " بههههترین راه ها واسه یاد گرفتن کد استایل خوب و الهام گرفتن واسه ی دولوپمنت ه . حالا راه های رسیدن به خدا خب زیاده مثلا جلسات کد ریویو با آدمای حرفه ای و متخصص و ... اما خب اینم یکیشه دیگه . البته گاها توی یسری مصاحبه ها هم ازش سوال میشه . پس اگه موافقین بریم که ببینیم داستان از چه قراره ^_^

اهم . اول اولش یه مرور ریزززز به شکل و شمایل این پترن بندازیم باشه؟

بفرمایین این دیاگرامش توی کتاب اول:

یه لطفی کنید و سمپل عادیشو که هم بصورت سودو کد هست هم به زبان های مختلف از جمله php پیاده شده رو ببینید اول حالا یا از این سایت:

https://refactoring.guru/design-patterns/strategy

یا توی همین ویرگول هم بگردین سمپل های فارسیش زیاده.

یه توضیح کوتاهم میدم اما بازم تاکید میکنم برای جزییات این پترن رجوووع شود به لینک زیر :

https://refactoring.guru/design-patterns/strategy

توضیح کوتاه و خودمونیش میشه که : وقتی قراره برای پیاده سازی یه راهکار توی برنامتون چندتا الگوریتم داشته باشین که بسته به شرایط ، بتونید براحتی و درواقع توی ران تایم انتخاب کنید که با کدوم الگوریتم (روش یا به اصطلاح استراتژی ) این قسمت از برنامتون اجرا بشه ، این پترن به کارتون میاد .البتهههه توجه فرااوان بفرمایین که این الگوریتما باید از یه خانواده باشن و درواقع بشه که بجای هم استفاده بشن . درواقع هرکدوم از این روش هارو یه کلاس Strategy میگن .به گفته ی خود کتاب هم:

Strategy is a behavioral design pattern that lets you define a

family of algorithms, put each of them into a separate class,

and make their objects interchangeable..

بنظرتون یکم شبیه composition نبودش؟ البته نه دیزاین پترنش . اون اصل Composition over inheritance . حالا شباهتشم باز تو همین ویرگول بگردین سمپل ازش هست بچه های دولوپر دیگه زحمت کشیدن و توضیحاشو گذاشتن دمشونم گرم و بد هم نیس که بدونید ;)

خیله خببب مثال بارز strategy توی لاراول ؟

دقت کردین که توی env. یه کلید داریم به اسم CACHE_DRIVER که مقادیر متفاوتی میتونیم بهش بدیم از جمله فایل یا ردیس یا دیتابیس و ...

بعدشم دقت کردین که همینکه شما اینجا درایور خودتون رو مشخص میکنید ، دیگه توی خود فریم وورک جاهایی که میخواین یه مقداری رو توش کش ذخیره کنید بسادگی میاین و مینویسین :

Cache::put("key", "value");

و انتظار دارین که این تیکه کد براتون به ازای این کلید ، این مقدار رو ، براساس چیزی که توی اون فایل env وارد کردین، ذخیره کنه . ینی درواقع ذخیره ی دیتا روی کش ، میتونه روش های متفاوتی داشته باشه که شما توی env تعیین میکنید کدوم یکی مدنظرتونه و توی ران تایم ،توی کلاسی که به اصطلاح در اون دیاگرام Context گفته میشه ، مورد استفاده قرار میگیره تا کلاس مربوط به این استراتژی رو تشخیص بده و بره تا متد put رو ازش کال کنه.

حالا اگه بخواین اینکارو روی دیتابیسی مث ردیس کنه ، یا روی فایل کنه ، این تیکه کدتون که دارین روی کش میریزین تغییری میکنه؟ (منظور همین Cach::put(....) هست ) . نخیر نخیییر ، واسه همینه که میگن : الگوریتما باید از یه خانواده باشن و درواقع بشه که بجای هم استفاده بشن. ینی چه بخوای الگوریتم ذخیره سازیش یا روش ذخیره سازیش ردیس باشه یا هرچیز دیگه ، کد اصلیش همینیه که میبینید . اینکه استراتژیش چی باشه (ممکنه جایی بجای کلمه ی استراتژی ، از کلمه ی روش و یا کلمه ی الگوریتم استفاده کنم که توی این مبحث منظور از همش یکیه) تاثیری روی فراخوانی متدی که قراره دیتا روی کش ذخیره کنه نداره . اع . پس کجا باید مشخص بشه؟ تووووووی کلاسی که به اصطلاح در اون کلاس دیاگرام بالا بهش میگن Context ست میشه . چجوری؟؟؟؟؟ خیله خب دیگه وقتشه که بریم واااارد لاراول بشیم ^_^ این وسط به یسری مفاهیم دیگه هم برمیخوریم که خب اونارو هم اشاره ی ریز میکنم و کوتاه میگم . مث فساد توی لاراول (با دیزاین پتن فساد فرق داره لطفا اشتباه نشود) و اورلودینگ توی پی اچ پی عزیز. پس فلن همین کد رو در نظر بگیرین:

Cache:put("name", "Raziyeh");

اسم خودمو ریختم توی کش . توی IDE خودتون اگر که لطف کنید و Crtl + click رو روی متد put بزنید میرید وارد کلاس Illuminate\Support\Facades\Cache میشین . انتظار میره که توی این کلاس یه متد استاتیک به اسم put باشه نه؟ نیست که . خب شاید تو کلاس پدرش باشه که ازش ارث برده ینی کلاس Facade . ای بابا اونجا هم نبود :( درعوضضضض تو این کلاس پدرش ینی کلاس Facade یه متد جذاب دیگه هست . در واقع یه مجیک متد جذاب به اسم __callStatic

که کار متد اورلودینگ واسه متدهای استاتیک رو برامون توی پی اچ پی انجام میده. (بنظرم کلا یکم اورلودینگ با توجه به مفهومش، تو php عجیبه نسبت به بقیه زبونا مث جاوا و اینا :دی ). مثلا اینجا که متد put نه توی کلاس اصلی وجود داشت و نه توی این کلاس پدرش ، توی ران تایم برامون این متد رو به اصطلاح resolve میکنه. چطوری؟ بریم ببینیم :

public static function __callStatic($method, $args) { $instance = static::getFacadeRoot(); if (! $instance) { throw new RuntimeException('A facade root has not been set.'); } return $instance->$method(...$args); }


خببببب شما خط اول رو داشته باش :

$instance = static::getFacadeRoot();

ایشون به ما یه instance از کلاس CacheManager میده . چجوری؟

اوه اوه واقعا بسختی. تو دلش یسری متد دیگه عاخه صدا میشه با کارای عجیب غریب :دی . تشریف بیارین از همینجا ینی همین کلاس Facade به متد getFacadeRoot ازینجا اول آرگومانش رو بگم :

public static function getFacadeRoot() { return static::resolveFacadeInstance(static::getFacadeAccessor()); }

اگه اون کلاس فرزند / ینی Cache که از این Facade ارث برده ، متد getFacadeAccessor رو داشته باشه (که تو این مثال داره) خروجیشو بهمون پس میده (که تو این مثال ینی : 'cache') :

protected static function getFacadeAccessor() { return 'cache'; }

پس با 'cache' میاد تو دل متد resolveFacadeInstance توی همون کلاس Facade .خببب . اینجاست که instance از CacheManager ساختع میشه. چطوری چطوری؟؟؟؟ شما بیا داخل متد resolveFacadeInstance و این تیکه رو خوووووب و قشنگ و با دقت نگاه کن :

static::$app[$name];

کل متدشم ایناها:

protected static function resolveFacadeInstance($name) { if (is_object($name)) { return $name; } if (isset(static::$resolvedInstance[$name])) { return static::$resolvedInstance[$name]; } return static::$resolvedInstance[$name] = static::$app[$name]; }

درواقع $app یه instance از Application هست که دسترسی به کانتینر لاراول رو فراهم میکنه. (کانتینر و اینا چیه؟ نه نه دیگه اینجا جاش نیست لطفا رجوع شود به داکیومنت خود لاراول که خیلی شیک و قشنگ توضیحش داده : https://laravel.com/docs/8.x/container)

و از اونجایی که موقع boot up یا همون بالا اومدن پروژه ، پرووایدر ها ی لاراول لود میشن و متد رجیسترشون اجرا میشهههه ، پس اگه چیزی توی یکی از این پرووایدرها و داخل متد رجیسترش به اصطلاح bind شده باشه، دیگه توی app instance بهش دسترسی داریم. که توی این مثال ، داخل CacheServiceProvider توی متد رجیسترش همچین چیزی وجود داره:

$this->app->singleton('cache', function ($app) { return new CacheManager($app); });

که داره به ازای اسم cache برامون یه instance از کلاس CacheManager با پترن singleton میسازه . پسسس هرجا لازم باشه میتونیم با استفاده از app instance یا حتی app('cache') هلپر و ... / به این آبجکت مورد نظرمون ، با اسمی که توی پرووایدر به ازاش مشخص شده (در اینجا 'cache') دسترسی داشته باشیم. ینی دقیقا کاری که توی کلاس Facade و داخل متد resolveFacadeInstance انجام شده بود : static::$app[$name]

(یکم بالاتر متدشو گذاشتم . البته خب توی کد لاراولتون هم میتونید این کلاسو اینجا ببینید: Illuminate\Support\Facades\Facade.php)

میتونید اصن خود static::$app رو هم اینجا تو کلاس Facade تو یکی از متداش مثلا dd کنید تا خودتون با چشم خویش ببینید که هم اون پرووایدر ها توش هست هم مثلا این استرینگ cache بعنوان یه alias توش وجود داره که گفتم میتونید بهش با روشای مختلف دسترسی داشته باشین.

حالا بیایم برگردیم ببینیم آخرر این متد put کجاست و چجوری لاراول خیلی شیک و قشنگ با استفاده از پترن Strategy صداش میزنه. پس برمیگردیم سر کلاس Facade و متد __callStatic که سلسله مراتبی رفتیم تو دلش و دیدیم که درواقع اون خط اولش ینی

$instance = static::getFacadeRoot();

داره بهمون آبجکت از CacheManager($app) میده . و بعدشم آخرای همون متد :

return $instance->$method(...$args);

اجرا میشه. متد چی بود اینجا؟ put . آرگوممان هاش چیا بودن ؟ name و Raziyeh .

خیله خب پس لابد الانه که داره از کلاس CacheManager متد put رو کال میکنه.کاش اونجااا دیگه همچین متدی باشه. اما وارد این کلاس که میشیم : Illuminate\Cache\CacheManager.php

میبینیم خعیررررر . اینجا هم خبری نیست . پس کجاست؟‌:( نیستش اینجام کلا الکی نگردیم دیگه . بجااااش دوباره یه مجیک متد دیگه هستش . به اسم: __call

public function __call($method, $parameters) { return $this->store()->$method(...$parameters); }

که این یکی هم کار اورلودینگ واسه متدهای غیر استاتیک رو توی php انجام میده. داخلش متد store از همین کلاس رو صدا میکنه . داخل این متد store دیگه ینی چه خبره؟

public function store($name = null) { $name = $name ?: $this->getDefaultDriver(); return $this->stores[$name] = $this->get($name); }

خلاصه خط اولش اگه که اسم درایور خالی باشه میره و از فایل کانفیگ cache.php درمیاره که درایور مدنظر چی ست شده؟ مثلا اینجا ، ردیس ست شده توی تنظیمات env مون که فایل کانفیگ cache رو هم اگه باز کنید میبینید اولویتش با چیزیه که توی env ست شده ،خلاصه توی خط بعدیش میفرستتش به این متد توی همون کلاس

$this->get('redis')

و بعدشم از تو متد get میاد و این متد resolve رو از همین کلاس صدا میزنه صدا میزنه که آهااان این متده جالبه:

protected function resolve($name) { $config = $this->getConfig($name); if (is_null($config)) { throw new InvalidArgumentException(&quotCache store [{$name}] is not defined.&quot); } if (isset($this->customCreators[$config['driver']])) { return $this->callCustomCreator($config); } else { $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; if (method_exists($this, $driverMethod)) { return $this->{$driverMethod}($config); } else { throw new InvalidArgumentException(&quotDriver [{$config['driver']}] is not supported.&quot); } } }


اصل اصل کارش اینجا این تیکه ست که :

$driverMethod = 'create'.ucfirst($config['driver']).'Driver';

اسم متد مورد نظر رو بر اساس چیزی که تو مراحل قبل از کانفیگ فایل کش بدست اورده پیدا میکنه (اینجا پس اسم متد میشه createRedisDriver) و بعدشم که صداش میکنه :

return $this->{$driverMethod}($config);

خیله خب پس تا اینجا بلخرررره رسیدیم به متد createRedisDriver. بریم ببینیم اینجا چه خبره؟؟؟

protected function createRedisDriver(array $config) { $redis = $this->app['redis']; $connection = $config['connection'] ?? 'default'; return $this->repository(new RedisStore($redis, $this->getPrefix($config), $connection)); }

بله بلهههه بلخره استراتژی مورد نظر تعیین میشه و خط آخرش هم متد repository از همین کلاس رو با آرگومانی که شامل کلاس استراتژی RedisStore هست صدا زده میشه. اما هنووووز متد put از این استراتژی صدا نزده شده . حالا تو خود متد repository هم نهایتا کلاس \Illuminate\Cache\Repository داره برمیگرده .خبببب . حالا این کلاس Repository چیه؟؟؟ دقیقا حکم اون کلاس Context رو توی اون شکل دیاگرامی که گذاشتم داره . اگر این کلاسش رو باز کنید ملاحظه میفرمایین که توی construct ش ،پارامتری که میگیره،کلاس استراتژی ای هستش که بالاتر بلخره کشفش کردیم و ست میشه توی پراپرتی store ش :

public function __construct(Store $store) { $this->store = $store; }

این تیکه ش هم همونطوری که از کلاس Context انتظار میره ، داره استراتژی مدنظر رو ست میکنه تا به وقتش با استفاده ازش نهااااااایتا اون متد put لامصب که میخاستیم کال بشه.الان میگم وقتش کی هست:

اگه برررگردیم به متدای بالاتری از کلاس CacheManager که هی کلی چیزای دیگه توش کال شده بود ، ینی متد __call میبینیم که بعلهه :

public function __call($method, $parameters) { return $this->store()->$method(...$parameters); }

این

$this->store()

ما شد همون آبجکت از کلاس ریپازیتوری با کانستراکتوری که کلاس RedisStore رو توی پراپرتی store ش ست میکنه و وقتی

$this->store()->method(...$parameters)

صدا زده میشه هم در واقعععع وارد کلاس RedisStrategy میشه و اینجاااااااااااااااس که بلخررره متد put ازش داره صدا زده میشه.

یه لحظه صبر کنید . یه بار سریع مرور کنیم از چه متدایی رد شد تا به اینجا رسید:

  • از کلاس CacheManager و متد __call رفت اول متد store
  • بعدش از متد store رفت متد get تو همون کلاس
  • بعدش از متد get رفت متد resolve تو همون کلاس
  • بعدش از تو متد resolve ، ازونجایی که درایور انتخابی ما ردیس بود(توی env مشخصش کرده بودم redis) رفتش متد createRedisDriver تو همون کلاس
  • بعدش از متد createRedisDriver رفت متد repository توی همون کلاس که دیگه خداروشکر به همینجا ختم شد و خروجی نهایی این متدکال های تودر تو اینجا شد یه آبجکت از کلاس

\Illuminate\Cache\Repository

با پارامتر کانستراکتورش که RedisStore شد و دیگههه این خروجی اومد بالا بالااا و رسید به دست متد اولیه که تو این کلاس کال شد ینی __call

هیچی خاستم یه دور سلسله مراتبشو سریع بگم که چه راه طولانی ای پشت سر گذاشت :دی

که حالا اگه خود کلاس RedisStore رو هم باز کنید میبینید که یه Interface رو داره implement میکنه به اسم Store که دقیییقا حکم اون Strategy Interface رو داره که شکل دیاگرامشو اون بالای پست گذاشته بودم . و وارد Interface ش هم که بشید میتونید با IDEتون چک کنید ببینید چه کلاس هایی implement ش کردن که اینجا شامل کلاسایی مثل : FileStore, RedisStore, ArrayStore, ApcStore, DatabaseStore و ... هست که این هاااا هم درواقع همون کلاس های concrete هستند که توی شکل دیاگرام نشون میداد.

عاقا تامام تامام . دوستم کنارم میگه داکیومنت لاراول رو اگه ترجمه میکردی الان تموم شده بود :))) ایشششالا که اینهمه نوشتم خوشتون اومده باشه و مفید واقع شه . فلن تا پست بعدی که نمیدونم کی باشه و کی وقت کنم باز بنویسم خدااانگهدار ... ;)



laravelstrategydesign pattern
یه بکند دولوپر :)
شاید از این پست‌ها خوشتان بیاید