DesignPatterns

デザインパターン勉強会 第4回:Factory Methodパターン

はじめに

本エントリーは某社内で実施するデザインパターン勉強会向けの資料となります。

本エントリーで書籍「Java言語で学ぶデザインパターン入門」をベースに学習を進めますが、サンプルコードはC#に置き換えて解説します。

第1回:Iteratorパターン

第2回:Adapterパターン

第3回:Template Methodパターン

Factory Methodパターンとは

スーパークラス側で処理の骨組みを作り、サブクラス側で具体的な処理の肉付けを行うTemplate Methodパターン (第3回で学習) をインスタンス生成の場面に適用したデザインパターンです。  

インスタンスの作り方をスーパークラス側で定め、具体的な肉付けはすべてサブクラス側で行います。


サンプルプログラムのクラス図

ここでは、身分証明書カード (IDカード) を作る工場を題材としてサンプルプログラムを紹介します。

class.PNG


各クラスの役割

名前空間 クラス名 役割
Framework Product 抽象メソッドUseのみ定義されている抽象クラス
Framework Factory メソッドCreateを実装している抽象クラス
Idcard IDCard メソッドUseを実装しているクラス
Idcard IDCardFactory メソッドCreateProduct,RegisterProductを実装しているクラス
Main MainTest 動作テスト用のクラス
  • Framework:インスタンス生成のフレームワークの側
  • IDCard:肉付けを行う側

Productクラス

「製品」を表現したクラスです。

このクラスでは、抽象メソッドUseのみが宣言されており、具体的な実装はすべてサブクラスにまかせています。

「製品」とは「Useできる(使える)もの」と規定していることとなります。

namespace Framework
{
    public abstract class Product
    {
        public abstract void Use();
    }
}

Factoryクラス

「工場」を表現したクラスです。

このクラスでは、抽象メソッドCreateProduct,RegisterProductが宣言されており、具体的な実装はすべてサブクラスにまかせています。

各メソッドの意味は以下のとおりです。

メソッド名 意味
CreateProduct 製品を作る
RegisterProduct 作った製品を登録する
Create Productのインスタンスを生成するもの。
「CreateProductで製品を作って、RegisterProductで作った製品を登録する」という手順として実装される。

「工場」とは「CreateメソッドでProductのインスタンスを生成するもの」と規定していることとなります。

namespace Framework
{
    public abstract class Factory
    {
        public Product Create(string owner)
        {
            Product p = CreateProduct(owner);
            RegisterProduct(p);
            return p;
        }

        protected abstract Product CreateProduct(string owner);
        protected abstract void RegisterProduct(Product product);
    }
}

IDCardクラス

製品Productクラスのサブクラスとして定義します。

using Framework;

namespace Idcard
{
    public class IDCard : Product
    {
        private string owner;

        public IDCard(string owner)
        {
            Console.WriteLine(owner + "のカードを作ります。");
            this.owner = owner;
        }

        public override void Use()
        {
            Console.WriteLine(owner + "のカードを使います。");
        }

        public String GetOwner()
        {
            return owner;
        }
    }
}

IDCardFactoryクラス

工場Factoryクラスのサブクラスとして定義します。

CreateProductでは、「製品を作る」ことを実現しています。

RegisterProductでは、IDCardのowner(所有者)をownersフィールドに追加して「登録」という機能を実現しています。

using Framework;

namespace Idcard
{
    public class IDCardFactory:Factory
    {
        private List<string> owners = new List<string>();

        protected override Product CreateProduct(string owner)
        {
            return new IDCard(owner);
        }

        protected override void RegisterProduct(Product product)
        {
            owners.Add(((IDCard)product).GetOwner());
        }

        public List<string> GetOwners()
        {
            return owners;
        }
    }
}

MainTestクラス

名前空間FrameworkとIdcardを利用して、実際のIDCardを作ります。

using Framework;
using Idcard;

namespace Main
{
    public class MainTest
    {
        public static void Main(string[] args)
        {
            Factory factory = new IDCardFactory();
            Product card1 = factory.Create("結城浩");
            Product card2 = factory.Create("とむら");
            Product card3 = factory.Create("佐藤花子");

            card1.Use();
            card2.Use();
            card3.Use();
        }
    }
}

実行結果

先のMainTestを実行した結果は以下の通りです。

結城浩のカードを作ります。
とむらのカードを作ります。
佐藤花子のカードを作ります。
結城浩のカードを使います。
とむらのカードを使います。
佐藤花子のカードを使います。

Factory Methodパターンの特徴

登場人物

今回のサンプルプログラムの登場人物とその役割を以下にまとめます。

人物 役割
Product(製品) フレームワーク側。
Factory Methodパターンで生成されるインスタンスが持つべきインターフェースを定める抽象クラス。
具体的な内容はサブクラスのIDCardが定める。
Factory(工場) フレームワーク側。
Productを生成する抽象クラス。
具体的な内容はサブクラスのIDCardFactoryが定める。
IDCard 具体的な肉付けをする側。
具体的な「製品の内容」を定める。
IDCardFactory 具体的な肉付けをする側。
工場で行う、「製品を作って登録する」という機能を実現する。

特徴

Factoryは、具体的に内容を定めるIDCardFactoryについて何も知らない。

Factoryが知っているのは、Productとインスタンス生成のメソッド(CreateProductメソッド)を呼び出せば、Productが生成するということのみ。

  • 意図
    newによる実際のインスタンス生成を、インスタンス生成のためのメソッド呼び出しに代えることで、具体的なクラス名による束縛からスーパークラスを開放している。

メリット

Factory Methodパターンには、Framework名前空間の内容を修正せずに全く別の「製品」と「工場」を作ることができる、というメリットがあります。

今回のサンプルプログラムは身分証明書(IDカード)を作る工場を題材としたものであり、FrameworkとIdcardという2つの名前空間に分けて実装を行いました。
Frameworkの中ではIdcardをインポートしていません。

したがって、新しいクラスを同じフレームワークで生成する場合でも、Frameworkの中身の修正は全く不要です。

例えば、「テレビを作る工場」という全く別のテーマとなった場合、Frameworkをインポートした別のTelevison名前空間を作ればよい、ということです。


サンプルコード

以下に公開しています。

https://github.com/ayayo/FactoryMethodPattern