Skip to content

Factory

Mostafa Miri edited this page Jul 19, 2022 · 19 revisions

الگوی طراحی Factory

الگوی طراحی کارخانه در دسته [الگوهای طراحی سازنده] یا Creational قرار دارد و به شما در مدیریت ایجاد اشیا کمک می‌کند. این الگوی طراحی، راهکاری در اختیار برنامه نویسان قرار می‌دهد تا آن‌ها را قادر سازد که بدون اینکه کلاس دقیق یک شی را مشخص کنند آن را ایجاد کنند و به استفاده از آن بپردازند. در ادامه این مقاله با الگوی طراحی Factory Method آشنا خواهیم شد و خواهیم دید چه زمانی استفاده از این الگوی طراحی در کدنویسی به کمک ما خواهد آمد.

چرا باید از الگوی طراحی Factory استفاده کنیم.

در بعضی موارد اپلیکیشن‌های ما بسیار بزرگ و پیچیده هستند و یا در آینده گسترش خواهند یافت. معمولا این اپلیکیشن‌ها دارای تعداد خیلی زیادی کلاس (Class) هستند که از همه آن‌ها نمونه و شی (Object) ساخته می‌شود. تصور کنید که اپلیکیشن شما دارای 500 کلاس باشد که از هر کدام از آنها باید به صورت میانگین 10 شی و نمونه ساخته شود. بنابراین در نرم افزار حدود 5000 هزار شی از کلاس‌ها مختلف ساخته شده است. مثلا کلاسی مثل User که در سرتاسر اپلیکیشن مورد استفاده قرار می‌گیرد.

بنابراین اگر مثلا بخواهیم به Constructor مربوط به کلاس User پارامتری جدید اضافه کنیم، باید کلاس User را تغییر دهیم. این کار باعث می‌شود که در تمام پروژه، هر جایی که از آن کلاس شی ای ساخته شده است پارامتر جدید را مقداردهی و اضافه کنیم که کاری بسیار دشوار و زمان بر خواهد بود. اما با استفاده از الگوی طراحی Factory برای ساخت اشیا کارخانه ای خواهیم ساخت که وظیفه ساخت اشیا از کلاس‌های مختلف را بر عهده دارد. با این کار دیگر نیازی به ساخت شی‌ها با استفاده از کیورد new به صورت جداگانه نخواهیم داشت و کافی است برای ساخت اشیا از کارخانه ساخت آن کلاس استفاده کنیم.

مثلا برای ساخت شی‌ها و نمونه هایی از کلاس User از Userfactory استفاده می‌کنیم. با این کار اگر نیاز به اعمال تغییراتی در کلاس مورد نظر خود داشته باشیم، کافی است تغییرات در کارخانه یا Factory مربوط به آن کلاس اعمال کنیم. پس از این عمل، تغییرات اعمال شده توسط کارخانه بر تمام کلاس‌های ساخته شده در آن، لحاظ می‌شوند.

چه زمانی باید از الگوی طراحی Factory استفاده کنیم.

کاربرد این الگوی طراحی برای شرایطی است که چندین کلاس ما از یک کلاس دیگر ارث بری کرده باشند و معمولا از آن کلاس نمونه یا شی ساخته می‌شود. همچنین این الگوی طراحی در موارد دیگر هم نیز استفاده می‌شود. این موارد عبارتند از :

کلاس هایی داشته باشیم که از آنها شی‌های زیادی ساخته می‌شود. با این کار برنامه نویس دیگر اشیا را ایجاد نخواهد کرد بلکه تمام مسئولیت ایجاد کلاس را به Factory Method واگذار می‌کند.

قبل از ایجاد شی هایی که نرم افزار با آن‌ها کار می‌کند، نوع و وابستگی مربوط به آن‌ ها را ندانید.

می خواهید با استفاده از اشیا موجود و جلوگیری از ساختن دوباره آن ها، در منابع سیستم صرفه جویی کنید.

از این الگو زمانی استفاده می‌کنیم که آبجکت‌های متنوعی وجود داره و تا قبل از اجرای برنامه نمی‌دونیم چه نوع آبجکتی و به چه صورتی قراره استفاده بشه. همچنین می‌خوایم راهی وجود داشته باشه که بتونیم بی‌نهایت نوع آبجکت داشته باشیم که هر کدوم نحوه ساخت متفاوتی دارن.

مثالی از کاربرد الگوی طراحی Factory


تصور کنید که وظیفه طراحی نرم افزاری برای مدیریت حمل و نقل اجناس تولیدی یک کارخانه بر عهده شما باشد. اولین نسخه ای که از این نرم افزار می‌سازید فقط می‌تواند حمل و نقل جاده ای را مدیریت کند. بنابراین بخش عمده ای از کدهای نوشته شده توسط شما در کلاس کامیون (Truck) نوشته شده اند. سپس بعد از مدتی نرم افزاری که طراحی کرده اید مورد توجه کاربران و مدیران کارخانه قرار می‌گیرد و به این دلیل تعداد زیادی درخواست برای توسعه نرم افزار به منظور پشتیبانی از مدیریت حمل و نقل دریایی دریافت می‌کنید.


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

نتیجه گیری

پیاده سازی الگوی طراحی Factory برای نرم افزارهایی که امکان دارد در آینده گسترش یابند بسیار مفید است. پیاده سازی این الگوی طراحی در نرم افزارها به افزایش انعطاف آن‌ها در برابر تغییرات بسیار کمک می‌کند. همچنین این الگو باعث بهینه سازی در مصرف منابع خواهد شد.

مثال

class Developer {
    constructor(name, type) {
        this.name = name
        this.type = type
    }
}

class Tester {
    constructor(name, type) {
        this.name = name
        this.type = type
    }
}

class EmployeeFactory {
    static create(name, type) {
        switch(type) {
            case 'developer':
                return new Developer(name, type)
            case 'tester':
                return new Tester(name, type)
        }
     }
}

const dev1 = EmployeeFactory.create('Ali', 'developer')
const dev2 = EmployeeFactory.create('mostafa', 'developer')
const tester1 = EmployeeFactory.create('ahmad', 'tester')
const tester2 = EmployeeFactory.create('hesam', 'tester')

for(const employee of [dev1, dev2, tester1, tester2]) {
    console.log(employee)
}

مثال دوم

فرض کنیم یک شرکت ارسال کالا داریم که در حال حاضر فقط به صورت موتوری فعالیت می‌کنه. کد زیر رو در نظر بگیرید.

class Delivery {
  public handle() {
    const bike = new Bike();
    bike.setMode('eco');
    bike.move();
  }
}

const init = new Delivery();
init.handle();

توی متد handle ما از کلاس Bike یک نمونه می‌سازیم و بعد کارهای آماده‌سازی ارسال رو انجام می‌دیم. تا اینجا همه چیز خیلی خوب و بدون مشکل پیش میره.

مدتی بعد که شرکت رشد کرد، می‌خوایم یک روش ارسال دیگه اضافه کنیم. این‌بار با اتومبیل. خب برای این‌کار باید متد handle رو مقداری دستکاری کنیم:

class Delivery {
  public handle(mode) {
    if (mode === 'bike') {
      const bike = new Bike();
      bike.setMode('eco');
      bike.move();
    } else if (mode === 'car') {
      const car = new Car();
      car.setColor('green');
      car.move();
    }
  }
}

const init = new Delivery();
init.handle('car');

همونطور که می‌بینیم متد handle مقداری شلوغ شد. همچنین داره کارهایی رو انجام میده که خارج از وظیفه اون هست؛ مثل ساختن و شخصی‌سازی وسیله نقلیه (اصل اول SOLID ؟!)

خب اگه چند روش ارسال دیگه بخوایم اضافه کنیم چطور؟ مثلاً قطار و هواپیما. می‌تونید تصور کنید که چقدر متد handle شلوغ و کد غیر قابل توسعه میشه؟ (اصل دوم SOLID ؟!) برای حل این مشکل، الگوی Factory Method به کار ما میاد 👌

class Delivery {
  public makeVehicle(mode) {
    if (mode === 'bike') {
      const bike = new Bike();
      bike.setMode('eco');
      
      return bike;
    } else if (mode === 'car') {
      const car = new Car();
      car.setColor('green');

      return car;
    }
  }
  
  public handle(mode) {
    const vehicle = this.makeVehicle(mode);
    vehicle.move();
  }
}

const init = new Delivery();
init.handle('bike');

اینجا یک متد ساختیم به اسم makeVehicle که مخصوص ساختن آبجکت‌ها هست. این همون متد Factory هست. توی نگاه اول شاید بگیم که کار بیهوده‌ای انجام دادیم: یه متد ساختیم و فقط کدها رو جابجا کردیم. به هر حال یک متد دیگه مسئول ساختن آبجکت‌ها شد و این الگو یعنی همین.

اما این پایان کار نیست. بجای اینکه آبجکت‌ها رو به صورت مستقیم توی متد makeVehicle بسازیم، می‌تونیم برای هر نوع آبجکت، یک زیرکلاس اختصاصی از کلاس Delivery بسازیم تا با رونوشت (Override) کردن متد فکتوری، کار ساختن آبجکت واگذار بشه به زیر کلاس‌ها.

ابتدا کلاس اصلی یعنی Delivery رو آماده انجام این کار کنیم:

abstract class Delivery {
  public abstract makeVehicle();

  public handle() {
    const vehicle = this.makeVehicle();
    vehicle.move();
  }
}

متد فکتوری یعنی makeVehicle رو از نوع Abstract کردیم تا زیرکلاس‌ها مجبور باشن اون رو پیاده‌سازی کنن. حالا برای هر نوع آبجکت (روش ارسال)، یک زیر کلاس می‌سازیم:

class BikeDelivery extends Delivery {
  public makeVehicle() {
    const bike = new Bike();
    bike.setMode('eco');

    return bike;
  }
}

class CarDelivery extends Delivery {
  public makeVehicle() {
    const car = new Car();
    car.setColor('green');

    return car;
  }
}

همونطور که می‌بینیم، کار ساختن آبجکت‌ها رو واگذار کردیم به زیر کلاس‌ها و اینطوری می‌تونیم بی‌نهایت روش ارسال داشته باشیم بدون اینکه کدهامون رو دستکاری کنیم.

حالا با توجه به روش ارسال مدنظرمون، یکی از این زیرکلاس‌ها رو پیاده‌سازی و استفاده می‌کنیم:

const byCar = new CarDelivery();
byCar.handle();

// ...

const byBike = new BikeDelivery();
byBike.handle();

خب این بود الگوی فکتوری متد. متد makeVehicle مسئول ساختن آبجکت‌ها شد و متد handle بدون اطلاع از اینکه آبجکت‌ها به چه صورت ساخته میشن، کار خودش رو انجام میده.

اما این کد هنوز ۱۰۰٪ قابل اعتماد نیست! چرا؟ کلاس‌های Bike و Car از هیچ اینترفیسی تبعیت نمی‌کنن. متد handle رو در نظر بگیرید.

از کجا می‌دونیم آبجکتِ ساخته‌شده متدی به اسم move داره؟ مطمئن نیستیم مگر اینکه که یک [اینترفیس] وجود داشته باشه. خب ابتدا باید برای کلاس‌های Car و Bike یک اینترفیس بسازیم:

interface Vehicle {
  setMode(mode);
  move();
}

class Bike implements Vehicle {
  setMode(mode) { /* */}

  move() { /* */ };
}

class Car implements Vehicle {
  setMode(mode) { /* */ }

  move() { /* */ }

  setColor(color) { /* */ }
}

چون متد فکتوری قراره آبجکت‌هامون (Bike یا Car) رو بسازه و تحویل بده، نوع خروجی اون رو برابر با این اینترفیس قرار می‌دیم (خط ۲):

abstract class Delivery {
  public abstract makeVehicle(): Vehicle;

  public handle() {
    const vehicle = this.makeVehicle();
    vehicle.move();
  }
}

خب اینجا دیگه کد ما تکمیل و قابل استفاده شد با جدا کردن قسمت‌های کد (به قول معروف Decoupling) تونستیم کدهایی تمیزتر، خواناتر و با قابلیت توسعه و نگهداری بالاتر بسازیم.تونستیم کاری کنیم که بتونیم بی‌نهایت نوع بسازیم بدون اینکه کدهامون رو دستکاری کنیم (اصل Open Closed) با انتقال دادن مسئولیت ساختن آبجکت‌ها به متد فکتوری، کاری کردیم که متد handle فقط مسئول انجام یک وظیفه باشه (اصل Single Responsibility) تا قبل از پیاده‌سازی این الگو، کلاس Delivery وابسته به کلاس Bike یا Car بود. اما بعد از اعمال این الگو، این وابستگی از بین رفت (اصل Dependency Inversion)

مثال سوم

فرض کنیم حالا دو تا کلاس دیگه داریم، یکی ماشین و یکی دیگه موتور که هردوتاشون نوعی وسیله ی نقلیه هسستن.میخواهیم با زبان جاوا کارخانه ای برای تولید اشیا بسازیم.

public interface Vehicle {
    void showVehicleType();
}
public class Car implements Vehicle {
    @Override
    public void showVehicleType() {
        System.out.println("Vehicle: Car");
    }
}
 public class MotorCycle implements Vehicle {
     @Override
     public void showVehicleType() {
         System.out.println("Vehicle: MotorCycle");
     }
 }
public class VehicleFactory {
    public enum VehicleTypeEnum{
        CAR,
        MOTOR_CYCLE
    }
    public Vehicle createVehicle(VehicleTypeEnum vehicleTypeEnum){
        if(vehicleTypeEnum== null){
            return null;
        } else if(vehicleTypeEnum.equals(VehicleTypeEnum.CAR)){
            return new Car();
        } else if(vehicleTypeEnum.equals(VehicleTypeEnum.MOTOR_CYCLE)){
            return new MotorCycle();
        }
        return null;
        }
} 

و زمان ساخت شئ به شکل زیر عمل کنیم:

Vehicle car = vehicleFactory.createVehicle(VehicleFactory.VehicleTypeEnum.CAR);

البته برای اینکه اصل open/close از اصول پنجگانه solid رو هم رعایت کرده باشیم میتونیم کلاس کارخونمون رو به شکل زیر پیاده سازی کنیم:

public class VehicleFactory {
    public Vehicle createCar(){
         return new Car();
    }
    public Vehicle createMotorCycle(){
        return new MotorCycle();
    } 
}

و طبیعتاً برای ساخت شئ به شکل زیر عمل میکنیم:

Vehicle carObject = vehicleFactory.createCar();
Vehicle motorCycleObject = vehicleFactory.createMotorCycle();

منبع

1-https://7learn.com/blog/factory-design-patterns

2-https://ditty.ir/posts/factory-method-design-pattern/XldZX

3-https://virgool.io/@mohammad.ghodsian/java-factory-design-pattern-h2loz1jfmkfh

Is necessary

Design Pattern

Creational

Structural

Behavioral

Template

Clone this wiki locally