دیزاین پترن Factory Method یک دیزاین پترن خلاقانه می باشد که یک اینترفیس برای ساخت اشیا در یک سوپر کلاس را فراهم می کند، اما اجازه می دهد که کلاس های فرزند ( زیر کلاس ها ) نوع اشیاءی که ساخته می شوند را تغییر دهند.
تصور کنید که شما در حال ساخت یک اپلیکیشن مدیریت لوجستیک هستید. اولین ورژن از اپ شما تنها قادر به هندل کردن حمل و نقل با کامیون ها می باشد، بنابراین بخش عمده ای از کد شما در کلاس Truck قرار می گیرد.
بعد از مدتی، اپ شما مورد توجه واقع می شود و بسیار محبوب می شود. هر روز ده ها درخواست از شرکت های حمل و نقل دریای برای وارد کردن تدارکات درایی در برنامه دریافت خواهید کرد.
و اما یک خبر عالی ! در حال حاضر اکثر کد شما با کلاس Truck همراه است. افزودن Ships به اپ نیازمند تغییرات زیاد در کدهای پایه ای شما دارد به علاوه اینکه اگر بعدا تصمیم به افزودن یک نوع جدید حمل و نقل در اپ داشته باشید شما مجدد مجبور هستید که همه این تغییرات را مجدد انجام دهید.
درنتیجه اینکه شما در این وضعیت با یک کد بسیار نامطبوع مواجه خواهید شد، کدی که رفتار برنامه را بسته به نوع کلاس اشیاء حمل و نقل تغییر می دهد.
پترن Factory Method به شما پیشنهاد می دهد که فراخوانی های ساخت یک شی به صورت مستقیم را ( با استفاده از کلمه new ) با فراخوانی یک متد factory خاص جایگزین کنید. نگران نباشید، اشیاء از طریق کلمه new ساخته می شوند، اما در داخل متد factory فراخوانی می شوند. اشیاء برگردانده شده توسط Factory Method اغلب به عنوان یک محصول ( products ) معرفی می شوند.
در نگاه اول،این تغییر ممکن است که بیهوده باشد: ما فقط فراخوانی تابع سازنده کلاس را از یک بخش از برنامه به بخش دیگری تغییر می دهیم. همچنین، این را در نظر بگیرید: حال شما می توانید که factory method را در یک زیر کلاس override کنید و کلاس محصولاتی که با این روش ایجاد شده است را تغییر دهید.
محدودیت های جزئی نیز وجود دارد: ممکن است زیر کلاس ها تنها در صورتی که محصولات کلاس پایه یا interface مشترکی داشته باشند انواع مختلفی از محصولات را برگردانند. همچنین، factory method در کلاس پایه بایستی نوع بازگشت خود را به عنوان این interface اعلام کند.
برای مثال: دو کلاس Truck و Ship بایستی با Interface Transport پیاده سازی شوند، که اعلام می کند یک متد deliver فراخوانی شود. هر کلاس این متد را متفاوت پیاده سازی می کند: trucks ها ( کامیون ها ) محموله ها را به صورت زمینی تحویل می دهند، ships ( کشتی ها )محموله ها را به صورت دریاییی تحویل می دهند. factory method در کلاس RoadLogistics اشیاء truck را بر می گرداند، درحالی که factory method در کلاس SeaLogistics اشیاء ships را بر می گرداند.
کدی که در factory method استفاده می شود ( که اغلب به آن کد client گفته می شود) تفاوت کالاهای واقعی برگشتی توسط زیر کلاس های مختلف را مشاهده نمی کند. client با تمام محصولات به صورت یک abstract Transport
رفتار می کند. client می داند که تمام اشیاء transport قرار بود متد deliver را داشته باشند، اما اینکه دقیقا چطور کار می کند برای client مهم نیست.
با توجه به عکس بالا :
۱ . در ابتدا interface Product اعلام می شود، که برای همه اشیائی که می تواند توسط creator و زیر کلاس های آن ساخته شود مشترک است.
۲ . در اینجا Concrete Products پیاده سازی های متفاوت از product interface هستند.
۳ . کلاس Creator مشخص می کند factory method اشیاء جدید product را بر می گرداند. این مهم است که نوع برگشتی این متد با product interface مطابقت دارد.
شما می توانید factory method را به عنوان abstract برای همه زیر کلاس ها اجبار کنید تا در نسخه های متد خود پیاده سازی کنند. به عنوان جایگزین، پایه factory method می تواند نوع محصول پیش فرض را برگرداند.
توجه داشته باشید، با وجود این نام گذاری، تولید محصول مسئولیت اصلی creator نیست.
معمولا، کلاس creator قبلا منطق اصلی تجارت مربوط به محصولات را داشته است. factory method به جدا کردن این منطق از کلاس concrete product کمک می کند. در اینجا یک آنالوژی هست: یک کمپانی بزرگ توسعه نرم افزار می تواند یک دپارتمان آموزشی برای برنامه نویسان داشته باشد. همچنین، عملکرد اصلی شرکت به عنوان یک مجموعه هنوز نوشتن کد است، نه تربیت و تولید برنامه نویس.
۴ . در آخرین قسمت Concrete Creators پایه factory method آورراید (override) می شود بنابراین آن یک نوع مختلف از محصول را بر می گرداند.
توجه داشته باشید که در factory method نیاز به ایجاد نمونه های جدید برای همیشه نیست. بلکه می توان اشیاء موجود را از یک کش، یک استخر شی یا منابع دیگر برگرداند.
این مثال نشان می دهد که چطور می توان از Factory Method برای ایجاد عناصر UI کراس پلتفرم بدون اتصال کد client به کلاس های concrete UI استفاده کرد.
کلاس پایه dialog عناصر UI مختلفی را برای ارائه دادن پنجره ( window ) استفاده می کند. تحت سیستم عامل های مختلف، این عناصر ممکن است اندکی به نظر برسند، اما آنها هنوز هم باید به طور مداوم رفتار کننده. یک دکمه در ویندوز هنوز یک دکمه در لینوکس می باشد و ماهیتشان یکی است.
زمانی که factory method وارد بازی شد، شما احتیاجی ندارید که منطق dialog را برای هر سیستم عامل بازنویسی کنید. اگر ما یک factory method معرفی کنیم که دکمه های داخل کلاس dialog پایه را بسازد، ما می توانیم بعدا یک زیر کلاس dialog بسازیم که دکمه هایی با استایل ویندوز را از factory method برگرداند. سپس زیرکلاس ما بخش زیادی از کد های dialog را از کلاس پایه به ارث می برد، اما، از factory method تشکر می کنیم، چرا که می تواند دکمه های ویندوز را در صفحه نمایش دهد.
برای اینکه این پترن کار کند، کلاس پایه dialog باید با abstract buttons کار کند: یک کلاس پایه یا یک interface که همه دکمه ها از آن پیروی کنند. بدین ترتیب کد dialog به صورت عملکردی باقی می ماند، طوری که هر نوع دکمه ای با آن کار می کند.
البته، شما می توانید این رویکرد را برای عناصر UI دیگر به خوبی درخواست کنید. همچنین، با اضافه کردن هر factory method جدید به dialog، شما به دیزاین پترن Abstract Factory نزدیک تر می شوید. نترسید، در ادامه در مورد این دیزاین پترن نیز صحبت خواهیم کرد.
شبه کد :
class Dialog is abstract method createButton():Button method render() is Button okButton = createButton() okButton.(closeDialog) okButton.render() class WindowsDialog extends Dialog is method createButton():Button is return new WindowsButton() class WebDialog extends Dialog is method createButton():Button is return new HTMLButton() interface Button is method render() method (f) class WindowsButton implements Button is method render(a, b) is // Render a button in Windows style. method (f) is // Bind a native OS click event. class HTMLButton implements Button is method render(a, b) is // Return an HTML representation of a button. method (f) is // Bind a web browser click event. class Application is field dialog: Dialog method initialize() is config = readApplicationConfigFile() if (config.OS == "Windows") then dialog = new WindowsDialog() else if (config.OS == "Web") then dialog = new WebDialog() else throw new Exception("Error! Unknown operating system.") method main() is this.initialize() dialog.render(
یک مثال مفهومی در زبان PHP :
<?php namespace RefactoringGuru\FactoryMethod\Conceptual; abstract class Creator { abstract public function factoryMethod(): Product; public function someOperation(): string { // Call the factory method to create a Product object. $product = $this->factoryMethod(); // Now, use the product. $result = "Creator: The same creator's code has just worked with " . $product->operation(); return $result; } } class ConcreteCreator1 extends Creator { public function factoryMethod(): Product { return new ConcreteProduct1; } } class ConcreteCreator2 extends Creator { public function factoryMethod(): Product { return new ConcreteProduct2; } } interface Product { public function operation(): string; } class ConcreteProduct1 implements Product { public function operation(): string { return "{Result of the ConcreteProduct1}" } } class ConcreteProduct2 implements Product { public function operation(): string { return "{Result of the ConcreteProduct2}" } } function clientCode(Creator $creator) { // ... echo "Client: I'm not aware of the creator's class, but it still works.\n" . $creator->someOperation(); // ... } echo "App: Launched with the ConcreteCreator1.\n" clientCode(new ConcreteCreator1); echo "\n\n" echo "App: Launched with the ConcreteCreator2.\n" clientCode(new ConcreteCreator2);
خروجی :
App: Launched with the ConcreteCreator1. Client: I'm not aware of the creator's class, but it still works. Creator: The same creator's code has just worked with {Result of the ConcreteProduct1} App: Launched with the ConcreteCreator2. Client: I'm not aware of the creator's class, but it still works. Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
یک مثال از دنیای واقعی در PHP :
<?php namespace RefactoringGuru\FactoryMethod\RealWorld; abstract class SocialNetworkPoster { abstract public function getSocialNetwork(): SocialNetworkConnector; public function post($content): void { // Call the factory method to create a Product object... $network = $this->getSocialNetwork(); // ...then use it as you will. $network->logIn(); $network->createPost($content); $network->logout(); } } class FacebookPoster extends SocialNetworkPoster { private $login, $password; public function __construct(string $login, string $password) { $this->login = $login; $this->password = $password; } public function getSocialNetwork(): SocialNetworkConnector { return new FacebookConnector($this->login, $this->password); } } class LinkedInPoster extends SocialNetworkPoster { private $email, $password; public function __construct(string $email, string $password) { $this->email = $email; $this->password = $password; } public function getSocialNetwork(): SocialNetworkConnector { return new LinkedInConnector($this->email, $this->password); } } interface SocialNetworkConnector { public function logIn(): void; public function logOut(): void; public function createPost($content): void; } class FacebookConnector implements SocialNetworkConnector { private $login, $password; public function __construct(string $login, string $password) { $this->login = $login; $this->password = $password; } public function logIn(): void { echo "Send HTTP API request to log in user $this->login with " . "password $this->password\n" } public function logOut(): void { echo "Send HTTP API request to log out user $this->login\n" } public function createPost($content): void { echo "Send HTTP API requests to create a post in Facebook timeline.\n" } } class LinkedInConnector implements SocialNetworkConnector { private $email, $password; public function __construct(string $email, string $password) { $this->email = $email; $this->password = $password; } public function logIn(): void { echo "Send HTTP API request to log in user $this->email with " . "password $this->password\n" } public function logOut(): void { echo "Send HTTP API request to log out user $this->email\n" } public function createPost($content): void { echo "Send HTTP API requests to create a post in LinkedIn timeline.\n" } } function clientCode(SocialNetworkPoster $creator) { // ... $creator->post("Hello world!"); $creator->post("I had a large hamburger this morning!"); // ... } echo "Testing ConcreteCreator1:\n" clientCode(new FacebookPoster("john_smith", "******")); echo "\n\n" echo "Testing ConcreteCreator2:\n" clientCode(new LinkedInPoster("john_smith@example.com", "******"));
خروجی :
Testing ConcreteCreator1: Send HTTP API request to log in user john_smith with password ****** Send HTTP API requests to create a post in Facebook timeline. Send HTTP API request to log out user john_smith Send HTTP API request to log in user john_smith with password ****** Send HTTP API requests to create a post in Facebook timeline. Send HTTP API request to log out user john_smith Testing ConcreteCreator2: Send HTTP API request to log in user john_smith@example.com with password ****** Send HTTP API requests to create a post in LinkedIn timeline. Send HTTP API request to log out user john_smith@example.com Send HTTP API request to log in user john_smith@example.com with password ****** Send HTTP API requests to create a post in LinkedIn timeline. Send HTTP API request to log out user john_smith@example.com
لینک گیت هاب جهت حمایت شما دوستان : لینک گیت هاب
منبع : ریفکتورینگ
گردآورنده : تیم فنی نتیرال
با مشاهده قسمت اول می توانید لینک های قسمته های بعد را نیز مشاهده کنید : قسمت اول