الگوی طراحی Facade چیست و به چه صورت در فرم ورک لاراول استفاده میشود


facade به معنی نما میباشد، دلیل استفاده از این الگوی طراحی به خاطر پنهان کردن بیشتر logic نرم افزار و ایجاد handler هایی میباشد که کاربر میتواند با استفاده از آنها به نتیجه اولیه دست پیدا کند ، facade  به عنوان یک در بر گیرنده یا یک wrapper کار میکند، که همه محتوا را در درون خود مخفی و متدهایی را بر اساس logic مخفی شده به کاربر نشان میدهد .

در قطعه کد پایین تیکه کدی از ارسال نامه اداری میباشد .

class LetterProcess{

public function sendLetter(){

        $inputs = $this->request->except(["_token"]);

        // check if letter going out
        if ($this->isGoingOut($inputs["send_type"])) {

            if ($this->hasPermission($this->auth->id, $inputs["send_type"]))
                $this->proccessLetter($inputs);

            else
                $this->sendLetterToManagerForAccepting($inputs);

        } else {

            if( $this->sendLetterToAnotherSection($inputs["to_id"]) ){

                if ($this->hasPermission($this->auth->id, $inputs["send_type"]))
                    $this->proccessLetter($inputs);
                else
                    $this->sendLetterToManagerForAccepting($inputs);
            } else
                  $this->proccessLetter($inputs);
        }
    }
}

این تیکه کد بخشی از ارسال نامه اداری میباشد و به این شکل همیشه ساده نیست ولی من جهت درک بیشتر مطلب ساده نوشتمش . خوب شما فرض کنید که عملیات دیگه ایی هم باید انجام بدیم مثل ایجاد step و ایجاد notification و ارسال ایمیل و ارجاع ، ارسال و کارهای دیگه ، خوب اگه بخوایم از facade استفاده کنیم . خیلی راحت میتونیم به صورت زیر عمل کنیم .

class sendLetterFacade{

    public function process(){
        $letter = new LetterProcces();
        $letter->sendLetter();
    }
}

همانطور که از کد بالا میتونید ببینید، کلاس facade به عنوان یک wrapper میباشد که فقط یک متد به نام process دارد، این متد تمامی کارای که باید انجام بشه رو انجام میده، ولی در صورتی که به متدهای بیشتری جهت انجام کار نیاز داشته باشیم ، میتونیم متدهای دیگری را ایجاد کنیم ، که البته باید معنی این متد به کاری که میخواد انجام بشه نزدیک باشه و همچنین اینرو بهم باید یادمون باشه که نباید کلاس facade رو با متدهای زیادی پیچیده اش کنیم چون دلیل ایجاد کلاس پنهان کردن logic است که در کلاسهای قبلی استفاده شده است .

#facade کلاس در لاراول

لاراول همچنان از این الگوی طراحی در فرم ورک خود استفاده کرده است . قبل از اینکه بگیم چطور میشه که یک facade کلاس در لاراول ایجاد کرد اول ببینیم که به چه شکل لاراول با facade کلاسها برخورد میکند .

ما فرض میکنیم که از یک facade کلاس به نام Route در لاراول استفاده میکنیم .

Route::get("post", "PostController@getIndex")->name("post.index");

خوب اگه این کلاس رو پیدا کنیم .

class Route extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'router';
    }
}

همانطور که از کد بالا میشه فهمید فقط یک کلاس که یدونه متد هم بیشتر نداره که اسمش هم هست getFacadeAccessor توی کلاس Route وجود دارد، خوب پس کد اصلی این کلاس کجاست ، همه متدها کجان ، Route::get و Route::post چطوری صدا زده میشه . خوب کلاس Route از کلاس Facade ارث بری انجام میدهد، بزارید یه نگاهی به کلاس Facade بندازیم .

abstract class Facade
{


    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     *
     * @throws \RuntimeException
     */
    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);
    }
}

خوب توی این کلاس لاراول از این magic متد جهت صدا زدن اون متد get از کلاس Route استفاده میکند. بعد از اجرا شدن این متد در اولین خط متد getFacadeRoot صدا زده میشود .

 /**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {

        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

همانطور که از کد بالا مشخص است این متد بعد صدا زده شدن ، یک کار را انجام میدهد ، که اون هم صدا زدن متد دیگری به نام ResolveFacadeInstance میباشد. اگه به آرگومان ورودی این متد توجه کنید ، از متد getFacadeAccessor استفاده شده است که این متد نام کلاس Router را بر میگرداند، ، چند خط بالا بهش اشاره شد . پس نام کلاس Router به متد resolveFacadeInstance ارسال میشود .

 /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object  $name
     * @return mixed
     */
    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];
    }

خوب همانطور که از کد بالا مشخص است ، بعد از ارسال نام کلاس که Router میباشد ، چک میشود که آیا نام مورد نظرObject است یا خیر ، سپس چک میشود که آیا در خصوصیت resolvedIsntances وجود دارد یا خیر در صورت وجود نداشتن آن ، در ابتدا نام این Router را از Container بازگشایی میکند و سپس  در خصوصیت resolvedInstance ذخیره میشود .

قبل از اجرای متد facade ، کلاس Application  اجرا میشود ، و عملیاتی مثل bootstrap کردن و همچنین register کردن را شروع میکند، در پوشه config/app یک آرایه به نام aliases وجود دارد که از key=>value ایجاد شده است، این آرایه بعد از register شدن به ما قابلیت دسترسی به تمامی کلاسها درون خود را میدهد. بر فرض مثال ما یک کلاس به نام Route داریم که در صورت صدا زدن نام این کلاس ، این کلاس اشاره میکند به آدرس facades\route ، لاراول با استفاده از کلاس aliasLoader این نامها را register میکند .

'Route' => Illuminate\Support\Facades\Route::class,

بعد از register کردن alias ها ، سپس لاراول کلاسهای داخلی خود را ثبت میکنند ، این کلاسها در کلاس application و در متد   registerCoreContainerAliases میباشد . ما در متد getFacadeAccessor ، کلاسی به نام Router را برگرداندیم 

'router' => ['Illuminate\Routing\Router', 'Illuminate\Contracts\Routing\Registrar'],

در صورت نیاز به این کلاس لاراول خصوصیتی به نام Aliases در کلاس application  را بررسی میکند و سپس concrete کلاس را resolve  میکند . که همون Illuminate\Routing\Router . اون aliases که در فایل config/app وجود داشت را با این aliases اشتباه نگیرید ، اندیس aliases که در فایل config/app وجود داشت جهت register کردن facade کلاس استفاده میشود ولی خصوصیت aliases جهت resolve کلاسهایی میباشد که لاراول آنها را در container خود ثبت کرده است .

برگردیم به کلاس Facade

abstract class Facade
{


    /**
     * Handle dynamic, static calls to the object.
     *
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     *
     * @throws \RuntimeException
     */
    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);
    }
}

خوب بعد از اینکه متد getFacadeRoot صدا زده شد، نتیجه کد پایین به این متد برگشت داده میشود که حاوی کلاس Router میباشد .

return static::$resolvedInstance[$name] = static::$app[$name];

سپس با استفاده از متغیر instance میتوان به کلاس Router و متدهای این کلاس دسترسی پیدا کرد که نتیجه را با return به محلی که Route::get صدا زده شد برگشت داده میشود .

امیدوارم این آموزش واستون جالب بوده باشه .

مشکل ، مورد ، نظر ، ایده هر سوالی که به ذهنتون در مورد این آموزش میرسه لطفاً بیان کنید