🙆

Factoryパターン

2024/10/27に公開

Simple Factory、Abstract Factory、Factory Method

ソフトウェア設計において、インスタンス生成の責務をクライアントから分離するFactoryパターンは、多くの場面で役立ちます。ここでは、Simple Factory、Abstract Factory、Factory Methodの3つのパターンを紹介し、それぞれの使いどころとメリット・デメリットを解説します。

1.SimpleFactory

Simple Factoryは、インスタンスの生成ロジックをクライアントコードから分離する最もシンプルなパターンです。
クライアントコードは生成ロジックを知らずにインターフェース(もしくは抽象クラス)を通じてインスタンスにアクセスします。

  • 基本static
  • Factoryクラスで生成しているインスタンスについては、internalを使ってカプセル化しておく
  • Factory+生成するInterface(抽象クラス)だけpublicにしておく
  • 実装しやすくお手軽
// Simple Factoryの例
public static class NotificationFactory
{
    public static INotification CreateNotification(string type)
    {
        return type switch
        {
            "Email" => new EmailNotification(),
            "SMS" => new SmsNotification(),
            _ => throw new ArgumentException("Invalid notification type.")
        };
    }
}

// 利用例
var notification = NotificationFactory.CreateNotification("Email");
notification.Send("Hello, world!");

2. Abstract Factory

  • WindowsUIComponentFactoryとMacUIComponentFactoryの2種類あったとすると、その2種類のFactoryクラスの親クラスとしてインターフェース or 抽象クラスを設けます。2つのFactoryは同じインターフェース or 抽象クラスを継承しているので、利用側でFactoryの切り替えが可能です。
public interface IUIComponentFactory
{
    IButton CreateButton();
    ITextBox CreateTextBox();
}

public class WindowsUIComponentFactory : IUIComponentFactory
{
    public IButton CreateButton() => new WindowsButton();
    public ITextBox CreateTextBox() => new WindowsTextBox();
}

public class MacUIComponentFactory : IUIComponentFactory
{
    public IButton CreateButton() => new MacButton();
    public ITextBox CreateTextBox() => new MacTextBox();
}

// 利用例(IUIComponentFactoryで差し替えが可能)
IUIComponentFactory uiFactory = new WindowsUIComponentFactory();
var button = uiFactory.CreateButton();
button.Render();

3. Factory Method

Factory Methodは、インスタンス生成の責務をサブクラスに委譲することで、共通ロジックを抽象クラスに集約できるのが特徴です。
各サブクラスはFactoryMethodを実装し、状況に応じて異なるインスタンスを生成できます。
抽象クラス(Creator)で共通ロジックを実装し、サブクラスでインスタンス生成を行います。
便利ですが、クラスが増えて可読性が落ちるのがデメリットです。

public interface IReport
{
    void Prepare();
    void Export();
}

public class PdfReport : IReport
{
    public void Prepare() => Console.WriteLine("Preparing PDF Report");
    public void Export() => Console.WriteLine("Exporting PDF Report");
}

public class ExcelReport : IReport
{
    public void Prepare() => Console.WriteLine("Preparing Excel Report");
    public void Export() => Console.WriteLine("Exporting Excel Report");
}

public abstract class ReportGenerator
{
    public void GenerateReport()
    {
     //共通ロジックをかく
        Console.WriteLine("Starting report generation...");
        var report = CreateReport();
        
        try
        {
            report.Prepare();
            report.Export();
            Console.WriteLine("Report generated successfully.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Failed to generate report: {ex.Message}");
        }
    }

    protected abstract IReport CreateReport();
}

public class PdfReportGenerator : ReportGenerator
{
    //各サブクラスでインスタンスを生成
    protected override IReport CreateReport()
    {
        Console.WriteLine("Generating a PDF report instance.");
        return new PdfReport();
    }
}

public class ExcelReportGenerator : ReportGenerator
{
    //各サブクラスでインスタンスを生成
    protected override IReport CreateReport()
    {
        Console.WriteLine("Generating an Excel report instance.");
        return new ExcelReport();
    }
}

// 利用例
ReportGenerator generator = new PdfReportGenerator();
generator.GenerateReport();

generator = new ExcelReportGenerator();
generator.GenerateReport();

基本的には、お手軽さと柔軟性のトレードオフとなります。
適切に使い分けましょう。

Discussion