یک فنجان جاوا - دیزاین پترن ها - Facade

میتونین برای آشنایی با الگوهای طراحی (یا همون دیزاین پترن های) زبان جاوا به مقاله ی یک فنجان جاوا - دیزاین پترن ها Design Patterns مراجعه کنین.

(همونطور که گفته شده این الگو زیرمجموعه ی الگوهای ساختاری (Structural) هست)

الگوی Facade

بذارین با یه مثال شروع کنیم. این روزا خیلیا به فکر مهاجرت افتادن، حالا شما دو تا راه دارین، یکی اینکه خودتون بیفتین دنبال کاراتون و با کلی دوندگی سعی کنین نتیجه بگیرین، و راه دوم اینکه به یه وکیل، کار چاق کن یا شخصی مراجعه کنین که کارهای اصلی رو خودش براتون انجام بده. الگوی Facade کاری مشابه با روش دوم رو براتون انجام میده.
این الگو زمانی به کار میره که میخوایم پیچیدگی های قسمت های مختلف رو کاهش بدیم و کارایی رو ساده تر کنیم. در حقیقت این الگو، رابطی ایجاد میکنه که ارتباط با این رابط به سهولت صورت گرفته و این رابط تمامی پیچیدگی های احتمالی رو پیاده سازی و مدیریت میکند.
برای درک بهتر، کاری که Controller توی پترن MVC انجام میده یا حتی وقتی دارین از Dependency Injection استفاده میکنین، در حقیقت از مفهومِ الگوی Facade هم استفاده شده


خب بریم سراغ یه مثال ساده که برای این الگو خیلی از این مثال استفاده میکنن. جدای از اینکه ساخت شکل های مختلف چقد میتونه کار داشته باشه، میخوایم یه کاری کنیم که بتونیم فقط به یه کلاس بگیم چه شکلی میخوایم و اون کلاس بقیه کارارو انجام بده. با اینترفیس زیر شوع میکنیم:

public interface Shape {
   void draw();
}

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

public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Rectangle::draw()");
   }
}
public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}

این دو تا کلاس ساده هم کار خاصی نمیکنن، فقط اینترفیس Shape رو implement کردن و تابع draw مربوط به خودشون رو پیاده سازی کردن. خب حالا واسه اینکه الگوی Facade رو پیاده بکنیم، کلاسی مینویسیم برای کشیدن این شکلها، بصورت زیر:

public class ShapeMaker {
   private Shape circle;
   private Shape rectangle;

   public ShapeMaker() {
      circle = new Circle();
      rectangle = new Rectangle();
   }

   public void drawCircle(){
      circle.draw();
   }
   public void drawRectangle(){
      rectangle.draw();
   }
}

خب این کلاس زمانی که تابع سازندش صدا زده میشه، یه circle و یه rectangle میسازه، و هربار تابع drawCircle صدا زده بشه، تابع draw شئ circle رو، و هر بار که تابع drawRectangle صدا زده بشه، تابع draw شئ rectangle رو صدا میزنه.
به همین سادگی ما الگوی Facade رو پیاده سازی کردیم. بصورت زیر هم میتونیم کدهامون رو تست کنیم:

ShapeMaker shapeMaker = new ShapeMaker();

shapeMaker.drawCircle();
shapeMaker.drawRectangle();

و خروجی بصورت زیر هست:

Circle::draw()
Rectangle::draw()

برای مثال بعدی یه فودکورت رو در نظر بگیرین که رستوران های مختلف و طبیعتاً منوهای متفاوتی دارن. اول از همه اینترفیس رستوران رو ایجاد میکنیم: (توی این کد فرض کردیم کلاس Menus داریم برای منوها، و کلاس های VegMenu و NonVegMenu و BothMenu که از کلاس Menus ارث بردن و مشخصه که اولی منوی گیاهخواری هست، دومی غذای غیر گیاهی یا همون گوشتی، و سومی هم هر دو رو شامل میشه)

public interface Restaurant { 
    public Menus getMenus(); 
} 

این کلاس، تابعی داره که منوی رستوران رو قراره برگردونه. پس سه نوع رستوران هم تولید میکنیم، یکی رستورانی که غذا برای گیاهخواران داره، یکی رستورانی که غذاهای گوشتی داره، و آخری هم رستورانی که هم غذای گیاهی داره و هم گوشتی:

public class VegRestaurant implements Restaurant { 
    public Menus getMenus() { 
        return VegMenu(); 
    } 
} 
public class NonVegRestaurant implements Restaurant { 
    public Menus getMenus() { 
        return NonVegMenu(); 
    } 
}
public class VegNonBothRestaurant implements Restaurant { 
    public Menus getMenus() { 
        return BothMenu(); 
    }
} 

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

public class RestaurantKeeper { 
    public VegMenu getVegMenu() { 
        VegRestaurant v = new VegRestaurant(); 
        VegMenu vegMenu = (VegMenu)v.getMenus(); 
        return vegMenu; 
    } 

    public NonVegMenu getNonVegMenu() { 
        NonVegRestaurant v = new NonVegRestaurant(); 
        NonVegMenu NonvegMenu = (NonVegMenu)v.getMenus(); 
        return NonvegMenu; 
    } 
    
    public Both getVegNonMenu() { 
        VegNonBothRestaurant v = new VegNonBothRestaurant(); 
        BothMenu bothMenu = (BothMenu)v.getMenus(); 
        return bothMenu; 
    }	 
} 

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

 HotelKeeper keeper = new HotelKeeper(); 
         
 VegMenu v = keeper.getVegMenu(); 
 NonVegMenu nv = keeper.getNonVegMenu(); 
 BothMenu= keeper.getVegNonMenu(); 

بصورت کلی الگوی Facade شمایی شبیه شکل زیر داره:


به عنوان مثال آخر هم یه کامپیوتر رو در نظر بگیریم که از سایت ویکیپدیا برداشت شده. ما وقتی لپتاپ یا کامپیوتر یا حتی موبایلمون رو که خاموشه، روشن میکنیم، تنها کاری که میکنیم دادن دستور روشن شدن به دستگاهمونه، ولی در عمل یسری اتفاق باید بیفته، و این مثال دقیقاً مشابه کاریه که الگوی Facade برای ما انجام میده. پس از کلاسای اجزای کامپیوتر شروع میکنیم (فعلا فقط کلاسای CPU و HardDrive و Memory رو در نظر میگیریم)

class CPU {
    public void freeze() { ... }
    public void jump(long position) { ... }
    public void execute() { ... }
}

class HardDrive {
    public byte[] read(long lba, int size) { ... }
}

class Memory {
    public void load(long position, byte[] data) { ... }
}

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

class ComputerFacade {
    private final CPU processor;
    private final Memory ram;
    private final HardDrive hd;
    
    public ComputerFacade() {
        this.processor = new CPU();
        this.ram = new Memory();
        this.hd = new HardDrive();
    }
    
    public void start() {
        processor.freeze();
        ram.load(BOOT_ADDRESS, hd.read(BOOT_SECTOR, SECTOR_SIZE));
        processor.jump(BOOT_ADDRESS);
        processor.execute();
    }
}

و تمام. به همین راحتی ما زمانی که بخوایم دستگاهمون رو روشن بکنیم، فقط کافیه تابع start رو صدا بزنیم و مابقی جزییات به ما ربطی نداره و تابع start میتونه هر جزییاتی که میخواد داشته باشه. در حقیقت تنها کاری که ما میکنیم اینه:

ComputerFacade computer = new ComputerFacade();
...
computer.start();

همونطور که دیدیم برای کاربری که از کلاس ComputerFacade استفاده میکنه مهم نیست که چقدر پیچیدس یا چه جزییاتی داره، عملیات اصلی به عهده ComputerFacade هست و کاربر فقط با صدا زدن تابع start یا سایر توابعی مثل stop یا هر تابع مورد نیاز دیگه ای، کارها رو به عهده ComputerFacade میذاره و فقط نتیجش رو میبینه.


همونطور که دیدیم و فهمیدیم، این الگو قراره کارهای پیچیده رو ساده بکنه. برای مثال اگه شما میخواین یه کتابخونه توسعه بدین، طبیعتاً نباید کسایی که میخوان ازش استفاده بکنن دردسر زیادی داشته باشن و شما به کمک این الگو میتونین عملکردها رو به عهده کلاس یا کلاسهایی از پروژتون بسپرین و کاربر فقط با صدا زدن چند تابع بتونه به عملیات های مورد نظرش برسه. همچنین یکی دیگه از مزایای این الگو کاهش وابستگی به کدهای خارجی هست، و طبیعتاً تغییرپذیری راحت تر، بیشتر و بهینه تر میشه. همچنین وقتی شما از یه API استفاده میکنین در حقیقت خواسته یا ناخواسته این الگو پیاده سازی شده. مثال و مزیت زیاد میشه گفته، ولی بصورت خلاصه میگیم الگوی Facade پیچیدگی سامانه های بزرگ رو مخفی کرده و یه رابط ساده جهت تعاملات پیاده سازی میکنه.


منتشر شده در ویرگول توسط محمد قدسیان https://virgool.io/@mohammad.ghodsian

https://virgool.io/@mohammad.ghodsian/java-facade-design-pattern-cl0ni2k0gamt