درود دوستان! توی این قسمت میخوایم یک الگوی طراحی ساده و درعین حال پرکاربرد رو بررسی کنیم؛ یعنی Factory Method!
با مثال شروع میکنیم: فرض کنیم یک شرکت ارسال کالا داریم که در حال حاضر فقط به صورت موتوری فعالیت میکنه. کد زیر رو در نظر بگیرید:
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();
همونطور که میبینیم متد handle مقداری شلوغ شد. همچنین داره کارهایی رو انجام میده که خارج از وظیفه اون هست؛ مثل ساختن و شخصیسازی وسیله نقلیه (اصل اول SOLID ؟!)
خب اگه چند روش ارسال دیگه بخوایم اضافه کنیم چطور؟ مثلاً قطار و هواپیما. میتونید تصور کنید که چقدر متد handle شلوغ و کد غیر قابل توسعه میشه؟ (اصل دوم SOLID ؟!) برای حل این مشکل، الگوی Factory Method به کار ما میاد 👌
الگوی Factory Method
این الگو میگه به جای اینکه خودمون به صورت مستقیم آبجکتها رو بسازیم، کارِ ساختن آبجکتها رو ببریم توی یک متد دیگه. به این متد میگن Factory که توی کلاس اصلی نوشته میشه.
این الگو زیرمجموعه الگوهای Creational هست: الگوهایی که توی اونها ساختن آبجکت اتفاق میافته.
خب برای شروع بیاید مثالی که نوشتیم رو مقداری دستکاری کنیم:
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 رو در نظر بگیرید:
public handle() { const vehicle = this.makeVehicle(); vehicle.move(); }
از کجا میدونیم آبجکتِ ساختهشده متدی به اسم 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(); } }
خب اینجا دیگه کد ما تکمیل و قابل استفاده شد 👌 کد کامل این مثال رو از ببینین. Star هم بزنین ⭐😉
چه زمانی از الگوی Factory Method استفاده کنیم؟
از این الگو زمانی استفاده میکنیم که آبجکتهای متنوعی وجود داره و تا قبل از اجرای برنامه نمیدونیم چه نوع آبجکتی و به چه صورتی قراره استفاده بشه. (Bike یا Car؟) همچنین میخوایم راهی وجود داشته باشه که بتونیم بینهایت نوع آبجکت داشته باشیم که هر کدوم نحوه ساخت متفاوتی دارن.
مزایای الگوی Factory Method
با جدا کردن قسمتهای کد (به قول معروف Decoupling) تونستیم کدهایی تمیزتر، خواناتر و با قابلیت توسعه و نگهداری بالاتر بسازیم.
همچنین ۳ اصل از اصول پنجگانه SOLID رو پیادهسازی کردیم:
۱. با انتقال دادن مسئولیت ساختن آبجکتها به متد فکتوری، کاری کردیم که متد handle فقط مسئول انجام یک وظیفه باشه (اصل Single Responsibility)
۲. تونستیم کاری کنیم که بتونیم بینهایت نوع بسازیم بدون اینکه کدهامون رو دستکاری کنیم (اصل Open Closed)
۳. تا قبل از پیادهسازی این الگو، کلاس Delivery وابسته به کلاس Bike یا Car بود. اما بعد از اعمال این الگو، این وابستگی از بین رفت (اصل Dependency Inversion)
برای آشنایی با اصول SOLID میتونید مجموعه پستهای زیر رو بخونید:
خب دوستان امیدوارم از این قسمت استفاده کرده باشید. الگوهای دیگه هم زود منتشر میشن. منتظر نظراتتون هستم 😉💪
منابعی که برای این قسمت استفاده کردم:
