Factory Method

Factory Method

@CSharpGeeks

🎯Intent

ءFactory Method یک creational design pattern است که یک interface برای ایجاد اشیاء در کلاس والد فراهم می‌کند، ولی به subclassها اجازه می‌دهد نوع اشیاء ایجادشده را تغییر دهند.

❗Problem

تصور کنید که در حال توسعه یک logistics management application هستید. نسخه‌ی اولیه‌ی اپ شما فقط قابلیت حمل و نقل با trucks را دارد، بنابراین بخش عمده‌ی کد درون کلاس Truck قرار دارد.

بعد از مدتی، اپ شما محبوب می‌شود و هر روز ده‌ها درخواست از شرکت‌های حمل و نقل دریایی برای افزودن sea logistics دریافت می‌کنید.

Adding a new class to the program isn’t that simple if the rest of the code is already coupled to existing classes.


خبر خوب است، اما کد چطور؟ در حال حاضر، بیشتر کد شما به کلاس Truck متصل است. افزودن Ships به اپ نیازمند تغییر کل کد خواهد بود. علاوه بر این، اگر بعداً بخواهید نوع دیگری از حمل و نقل اضافه کنید، احتمالاً باید دوباره این تغییرات را اعمال کنید.

در نتیجه، کد شما به شدت coupled و پر از conditionals خواهد شد که رفتار اپ را بسته به کلاس اشیاء حمل و نقل تغییر می‌دهند.

💡Solution

الگوی Factory Method پیشنهاد می‌کند که calls مستقیم به سازنده‌ی اشیاء (با استفاده از new) را با calls به یک factory method ویژه جایگزین کنید. نگران نباشید: اشیاء هنوز با new ساخته می‌شوند، اما این فراخوانی درون factory method انجام می‌شود. اشیائی که توسط factory method برگردانده می‌شوند معمولاً به عنوان products شناخته می‌شوند.

Subclasses can alter the class of objects being returned by the factory method.

در نگاه اول، ممکن است این تغییر بی‌فایده به نظر برسد: فقط فراخوانی constructor از یک بخش برنامه به بخش دیگر منتقل شده است. اما توجه کنید: حالا می‌توانید factory method را در subclass override کنید و کلاس محصولات ایجادشده توسط متد را تغییر دهید.

📌 یک محدودیت کوچک وجود دارد: subclassها می‌توانند انواع مختلفی از محصولات را برگردانند تنها در صورتی که این محصولات یک base class یا interface مشترک داشته باشند. همچنین، factory method در کلاس والد باید نوع بازگشتی خود را به عنوان همان interface اعلام کند.

مثال عملی:

  • کلاس‌های Truck و Ship باید Transport interface را پیاده‌سازی کنند که متدی به نام deliver دارد.
  • هر کلاس این متد را به روش خود پیاده‌سازی می‌کند: trucks محموله را از طریق زمین تحویل می‌دهند، ships محموله را از طریق دریا.
  • factory method در کلاس RoadLogistics اشیاء Truck را برمی‌گرداند، در حالی که factory method در SeaLogistics اشیاء Ship را برمی‌گرداند.
As long as all product classes implement a common interface, you can pass their objects to the client code without breaking it.

کدی که از factory method استفاده می‌کند (معمولاً به آن client code می‌گویند) تفاوتی بین محصولات واقعی برگشتی از subclassهای مختلف نمی‌بیند. Client همه محصولات را به عنوان abstract Transport می‌شناسد. Client می‌داند که همه‌ی اشیاء transport باید متد deliver را داشته باشند، اما نحوه‌ی اجرای دقیق آن برای client اهمیتی ندارد.

❗Structure

🔹 Product

ءProduct رابطی (interface) را تعریف می‌کند که برای تمام اشیایی که توسط creator و subclassهای آن تولید می‌شوند مشترک است.

🔹 Concrete Products

ءConcrete Products پیاده‌سازی‌های مختلف Product interface هستند.

🔹 Creator

کلاس Creator factory method را تعریف می‌کند که اشیاء جدید Product را برمی‌گرداند. مهم است که نوع بازگشتی این متد با Product interface مطابقت داشته باشد.

  • می‌توان factory method را به صورت abstract تعریف کرد تا تمام subclassها مجبور به پیاده‌سازی نسخه‌ی خود شوند.
  • به‌عنوان جایگزین، base factory method می‌تواند نوع محصول پیش‌فرضی را برگرداند.

⚠️ توجه: با وجود نامش، ایجاد محصول مسئولیت اصلی creator نیست. معمولاً creator شامل منطق اصلی مربوط به محصولات است و factory method به جدا کردن این منطق از کلاس‌های Concrete Product کمک می‌کند.

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

  • ءConcrete Creators با override کردن base factory method نوع محصول را تغییر می‌دهند.
  • Factory method لازم نیست همیشه نمونه‌ی جدید بسازد؛ می‌تواند اشیاء موجود در cache، object pool یا منابع دیگر را برگرداند.

🔹 Pseudocode

این مثال نشان می‌دهد که چگونه Factory Method می‌تواند برای ایجاد cross-platform UI elements بدون couple شدن کد client با کلاس‌های UI مشخص استفاده شود.

The cross-platform dialog example.
  • کلاس پایه‌ی Dialog از عناصر UI مختلف برای رندر کردن پنجره استفاده می‌کند.
  • در سیستم‌عامل‌های مختلف، این عناصر ممکن است کمی متفاوت به نظر برسند اما باید رفتار یکسانی داشته باشند. (مثلاً یک دکمه در Windows هنوز دکمه است، حتی در Linux)

با ورود factory method، نیازی به بازنویسی منطق کلاس Dialog برای هر سیستم‌عامل نیست. اگر factory method برای تولید Button در کلاس پایه تعریف کنیم، می‌توانیم بعداً subclass ایجاد کنیم که Windows-styled Button برمی‌گرداند.

برای کارکرد این الگو، کلاس پایه‌ی Dialog باید با abstract Button کار کند (کلاس پایه یا interface که تمام دکمه‌های Concrete از آن پیروی می‌کنند).

  • این روش می‌تواند برای سایر عناصر UI نیز استفاده شود.
  • با اضافه کردن هر factory method جدید به Dialog، به الگوی Abstract Factory نزدیک‌تر می‌شویم (که بعداً توضیح داده می‌شود).

 C# Implementation

// Product Interface
public interface Button
{
    void Render();
    void OnClick(Action f);
}

// Concrete Products
public class WindowsButton : Button
{
    public void Render()
    {
        // Render a button in Windows style
        Console.WriteLine("Rendering Windows-style Button");
    }

    public void OnClick(Action f)
    {
        // Bind native OS click event
        f();
    }
}

public class HTMLButton : Button
{
    public void Render()
    {
        // Return an HTML representation of a button
        Console.WriteLine("Rendering HTML Button");
    }

    public void OnClick(Action f)
    {
        // Bind a web browser click event
        f();
    }
}

// Creator
public abstract class Dialog
{
    public abstract Button CreateButton();

    public void Render()
    {
        // Call the factory method to create a product object
        Button okButton = CreateButton();
        // Now use the product
        okButton.OnClick(() => Console.WriteLine("Closing dialog..."));
        okButton.Render();
    }
}

// Concrete Creators
public class WindowsDialog : Dialog
{
    public override Button CreateButton()
    {
        return new WindowsButton();
    }
}

public class WebDialog : Dialog
{
    public override Button CreateButton()
    {
        return new HTMLButton();
    }
}

// Client / Application
public class Application
{
    private Dialog dialog;

    public void Initialize()
    {
        string os = ReadApplicationConfigFile();

        if (os == "Windows")
            dialog = new WindowsDialog();
        else if (os == "Web")
            dialog = new WebDialog();
        else
            throw new Exception("Error! Unknown operating system.");
    }

    private string ReadApplicationConfigFile()
    {
        // Just for demonstration, return "Windows" or "Web"
        return "Windows";
    }

    public void Run()
    {
        dialog.Render();
    }
}

// Usage
class Program
{
    static void Main()
    {
        Application app = new Application();
        app.Initialize();
        app.Run();
    }
}

Applicability 🔹 کاربردها

📌 چه زمانی از Factory Method استفاده کنیم؟

  1. وقتی نوع دقیق اشیاء و وابستگی‌هایشان را از قبل نمی‌دانیم
  2. Factory Method کد ساخت محصول را از کدی که محصول را مصرف می‌کند جدا می‌کند.
  3. این باعث می‌شود توسعه و افزودن محصولات جدید بدون تغییر کد اصلی آسان‌تر باشد.
  4. مثال: اگر بخواهید نوع جدیدی از محصول به اپ اضافه کنید، کافی است یک subclass جدید از Creator بسازید و factory method آن را override کنید.
  5. وقتی می‌خواهید کاربران کتابخانه یا فریم‌ورک شما بتوانند اجزای داخلی را گسترش دهند
  6. ارث‌بری (Inheritance) ساده‌ترین راه برای گسترش رفتار پیش‌فرض است.
  7. اما چطور فریم‌ورک تشخیص دهد که subclass شما باید جایگزین یک کامپوننت استاندارد شود؟
  8. راه حل: همه کدهای ساخت کامپوننت را در یک factory method جمع کنید و اجازه دهید هر کسی آن را override کند.

💡 مثال عملی:

  • شما از یک open source UI framework استفاده می‌کنید که فقط دکمه‌های مربع ارائه می‌دهد.
  • یک کلاس RoundButton ایجاد می‌کنید.
  • سپس یک subclass از UIFramework می‌سازید به نام UIWithRoundButtons و createButton را override می‌کنید تا RoundButton برگرداند.
  • حالا کافی است از UIWithRoundButtons استفاده کنید تا دکمه‌های گرد را جایگزین پیش‌فرض کنید.
  1. وقتی می‌خواهید منابع سیستم را ذخیره کنید و اشیاء موجود را دوباره استفاده کنید
  2. مخصوصاً برای اشیاء بزرگ و resource-intensive مثل database connections, file systems و network resources.

مراحل استفاده از یک شیء موجود:

  • ابتدا جایی برای نگهداری اشیاء ایجاد کنید (object pool).
  • وقتی کسی درخواست شیء می‌کند، ابتدا به دنبال یک شیء آزاد در pool بگردید.
  • اگر شیء آزاد پیدا شد، آن را به client برگردانید.
  • اگر موجود نبود، یک شیء جدید بسازید و به pool اضافه کنید.

این دقیقاً همان چیزی است که factory method می‌تواند مدیریت کند: ساخت و بازاستفاده از اشیاء در یک مکان واحد.

🛠 How to Implement - مراحل پیاده‌سازی

  1. تمام محصولات باید از یک interface مشترک پیروی کنند که متدهای منطقی برای هر محصول را تعریف کند.
  2. یک factory method خالی در کلاس Creator اضافه کنید. نوع بازگشتی آن باید همان interface محصول باشد.
  3. در کد Creator، تمام فراخوانی‌های constructor محصولات را پیدا کرده و به تدریج با فراخوانی factory method جایگزین کنید.
  4. ممکن است نیاز به یک پارامتر موقت برای کنترل نوع محصول برگردانده شده باشد.
  5. بعد از آن، ممکن است کد factory method بزرگ و پیچیده شود (مثلاً شامل یک switch برای انتخاب کلاس محصول). نگران نباشید، بعداً با استفاده از subclassهای Creator این پیچیدگی را کاهش می‌دهیم.
  6. برای هر نوع محصول، یک subclass از Creator ایجاد کنید و factory method را override کنید.
  7. اگر تعداد محصول‌ها زیاد است و ایجاد subclass برای همه منطقی نیست، می‌توانید از پارامتر کنترل نوع محصول در base class استفاده کنید.

💡 مثال عملی:

  • سلسله مراتب کلاس‌ها: Mail با دو subclass: AirMail و GroundMail
  • کلاس‌های Transport: Plane, Truck, Train
  • AirMail فقط با Plane کار می‌کند، اما GroundMail با Truck و Train
  • می‌توان یک subclass جدید (TrainMail) ایجاد کرد، یا از پارامتر کنترل در GroundMail factory method استفاده کرد.
  • اگر بعد از استخراج، base factory method خالی شد، آن را abstract کنید.
  • اگر مقداری باقی مانده، می‌تواند به عنوان رفتار پیش‌فرض متد باشد.

⚖ Pros and Cons - مزایا و معایب

مزایا:

  • از tight coupling بین creator و Concrete Products جلوگیری می‌کند.
  • رعایت Single Responsibility Principle: کد ساخت محصول در یک مکان جمع می‌شود و نگهداری آسان‌تر می‌شود.
  • رعایت Open/Closed Principle: امکان افزودن محصولات جدید بدون شکستن کد موجود.

معایب:

  • پیچیدگی کد ممکن است افزایش یابد، زیرا نیاز به ایجاد تعداد زیادی subclass برای پیاده‌سازی الگو وجود دارد.
  • بهترین حالت، زمانی است که این الگو را در سلسله مراتب موجود کلاس‌های Creator معرفی می‌کنید.

Factory Method در C# 🔹

🎯 مفهوم

Factory Method یک creational design pattern است که مشکل ایجاد اشیاء Product بدون مشخص کردن کلاس‌های Concrete آن‌ها را حل می‌کند.

  • این الگو یک متد تعریف می‌کند که باید برای ایجاد اشیاء استفاده شود، به جای اینکه مستقیم از constructor (new) استفاده کنیم.
  • Subclasses می‌توانند این متد را override کنند تا کلاس اشیاء ایجادشده را تغییر دهند.

💻 موارد استفاده

  • وقتی نیاز به انعطاف‌پذیری بالایی در کد دارید.
  • وقتی می‌خواهید کاربران کتابخانه یا فریم‌ورک بتوانند behavior داخلی را گسترش دهند.
  • وقتی می‌خواهید tight coupling بین کد و کلاس‌های concrete کاهش یابد.

نکته شناسایی:

  • متدهای Factory معمولاً اشیاء را از کلاس‌های concrete می‌سازند، ولی return type آنها معمولاً abstract class یا interface است.

🏗 ساختار مفهومی (Conceptual Example)

این مثال ساختار الگوی Factory Method را نشان می‌دهد و به این سوالات پاسخ می‌دهد:

  • شامل چه کلاس‌هایی است؟
  • نقش این کلاس‌ها چیست؟
  • ارتباط اجزای الگو چگونه است؟

🔹 C# Implementation

using System;

namespace RefactoringGuru.DesignPatterns.FactoryMethod.Conceptual
{
    // Creator: کلاس پایه که Factory Method را تعریف می‌کند.
    abstract class Creator
    {
        public abstract IProduct FactoryMethod();

        // منطق اصلی که به Product وابسته است
        public string SomeOperation()
        {
            var product = FactoryMethod();
            var result = "Creator: The same creator's code has just worked with "
                + product.Operation();
            return result;
        }
    }

    // Concrete Creators: Factory Method را override می‌کنند
    class ConcreteCreator1 : Creator
    {
        public override IProduct FactoryMethod()
        {
            return new ConcreteProduct1();
        }
    }

    class ConcreteCreator2 : Creator
    {
        public override IProduct FactoryMethod()
        {
            return new ConcreteProduct2();
        }
    }

    // Product Interface
    public interface IProduct
    {
        string Operation();
    }

    // Concrete Products
    class ConcreteProduct1 : IProduct
    {
        public string Operation()
        {
            return "{Result of ConcreteProduct1}";
        }
    }

    class ConcreteProduct2 : IProduct
    {
        public string Operation()
        {
            return "{Result of ConcreteProduct2}";
        }
    }

    // Client: کد مشتری که با Creator کار می‌کند
    class Client
    {
        public void Main()
        {
            Console.WriteLine("App: Launched with the ConcreteCreator1.");
            ClientCode(new ConcreteCreator1());
            
            Console.WriteLine("");

            Console.WriteLine("App: Launched with the ConcreteCreator2.");
            ClientCode(new ConcreteCreator2());
        }

        public void ClientCode(Creator creator)
        {
            Console.WriteLine("Client: I'm not aware of the creator's class," +
                "but it still works.\n" + creator.SomeOperation());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Client().Main();
        }
    }
}


📝 خروجی برنامه

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 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 ConcreteProduc

برای مطالب بیشتر به چنل بپیوندید❤️

CSharpGeeks(.NET)

Report Page