← 一覧に戻る
生成に関するパターン

Factory Method

*インスタンス生成をサブクラスに任せる*

概要

インスタンス生成の方法をサブクラスで定めるが、具体的なクラス名は出さない

マンガでわかる Factory Method

マンガでわかる Factory Method #デザインパターン - Qiita

でざぱたんで覚える Factory Method

ちびキャラは「ファクトリメソッドたん」。注文内容から判断して期待以上の品を納める村の錬金術師。調合の過程(生成過程)は客に見せず、完成品だけを渡す——生成の隠蔽と集中管理がFactory Methodの本質で、ファクトリを差し替えるだけで利用側のコードを変えずに生成物を変えられる柔軟さも、彼女の腕前として描かれる。

出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.087〜)

登場人物

  • Product:生成されるインスタンスが持っているべきAPI
  • Creator: Productを生成する抽象クラス
  • ConcreteProductについては何も知らないことがミソでありこのパターンのメリット
  • ConcreteProduct
  • ConcreteCreator

クラス図

Factory Method Pattern

このサイトの実装(IDカード発行)での対応関係:

classDiagram
  class Factory {
    <<abstract>>
    +create(owner) Product
    #createProduct(owner) Product
    #registerProduct(product)
  }
  class Product {
    <<abstract>>
    +use()
  }
  class IDCardFactory {
    -owners
    #createProduct(owner) Product
    #registerProduct(product)
    +getOwners() List
  }
  class IDCard {
    -owner
    +use()
    +getOwner() String
  }
  Factory <|-- IDCardFactory
  Product <|-- IDCard
  Factory ..> Product : creates
  IDCardFactory ..> IDCard : creates

やり方

  • Creatorに以下を用意する
  1. Product型を返す抽象生成メソッド
  2. それを内部で呼び出す、protectedでfinalな具象メソッド
  3. その中で毎回行いたいprotectedな抽象メソッド(インスタンス生成時に必ずさせたい動作)

メリット(用途)

オブジェクトの生成過程の隠蔽と集中管理

オブジェクト生成の方法に注目したパターン

単純な生成クラスではなくテンプレートメソッド的なオブジェクト準備の過程をabsractメソッドに定義して、オブジェクト生成と準備を抽象化

多様なインスタンス生成を請け負うオブジェクト生成は強い依存を生むので、切り離すだけでも大きなメリットがある

つまり、インスタンス生成時に決まった手順でhogehogeするを抽象クラスから強制できる

Java
framework/Factory.java
package framework;

public abstract class Factory {
    public final Product create(String owner) {
        Product p = createProduct(owner);
        registerProduct(p);
        return p;
    }
    protected abstract Product createProduct(String owner);
    protected abstract void registerProduct(Product product);
}
framework/Product.java
package framework;

public abstract class Product {
    public abstract void use();
}
idcard/IDCardFactory.java
package idcard;
import framework.*;
import java.util.*;

public class IDCardFactory extends Factory {
    private List owners = new ArrayList();
    protected Product createProduct(String owner) {
        return new IDCard(owner);
    }
    protected void registerProduct(Product product) {
        owners.add(((IDCard)product).getOwner());
    }
    public List getOwners() {
        return owners;
    }
}
idcard/IDCard.java
package idcard;
import framework.*;

public class IDCard extends Product {
    private String owner;
    IDCard(String owner) {
        System.out.println(owner + "のカードを作ります。");
        this.owner = owner;
    }
    public void use() {
        System.out.println(owner + "のカードを使います。");
    }
    public String getOwner() {
        return owner;
    }
}
Main.java
import framework.*;
import idcard.*;

public class Main {
    public static void main(String[] args) {
        Factory factory = new IDCardFactory();
        Product card1 = factory.create("User1");
        Product card2 = factory.create("User2");
        Product card3 = factory.create("User3");
        card1.use();
        card2.use();
        card3.use();
    }
}
Go

Javaは「基底 Factory.create が抽象 createProduct を呼ぶ」テンプレート。継承の無いGoでは、共通関数 Create(Creator, owner) が引数 interface の CreateProduct/RegisterProduct を呼ぶ形(継承 → 合成)に置き換える。ダウンキャストは型アサーションで対応。

実行: go run ./GoF/patterns/FactoryMethod/go

別事例

$ go run ./GoF/patterns/FactoryMethod/go
framework.go
package main

// ---- framework 層(Java版の framework パッケージに相当)----

// Product は工場が生み出す製品の抽象。
type Product interface {
	Use()
}

// Creator は「製品の作り方」と「登録の仕方」を具体工場に要求するインタフェース。
// Java版 Factory の抽象メソッド createProduct / registerProduct に相当する。
type Creator interface {
	CreateProduct(owner string) Product
	RegisterProduct(p Product)
}

// Create は Factory Method の“骨組み”(Java版 Factory.create の final メソッド相当)。
// 「作って → 登録して → 返す」という流れは固定で、
// 実際の生成・登録の中身だけを Creator(具体工場)へ委ねる。
//
// Go には継承が無いので、
//
//	「基底クラスの create が サブクラスの createProduct を呼ぶ」
//
// を、
//
//	「共通関数 Create が 引数 Creator のメソッドを呼ぶ」
//
// に置き換える。継承ではなく合成で同じ効果を出すのが Go 流。
func Create(c Creator, owner string) Product {
	p := c.CreateProduct(owner)
	c.RegisterProduct(p)
	return p
}
idcard.go
package main

import "fmt"

// ---- idcard 層(Java版の idcard パッケージに相当)----

// IDCard は具体的な Product。
type IDCard struct {
	owner string
}

// newIDCard は非公開コンストラクタ。
// Java版も IDCard(String) がパッケージプライベートで、外から直接 new させない意図。
func newIDCard(owner string) *IDCard {
	fmt.Printf("%sのカードを作ります。\n", owner)
	return &IDCard{owner: owner}
}

func (c *IDCard) Use() {
	fmt.Printf("%sのカードを使います。\n", c.owner)
}

func (c *IDCard) Owner() string { return c.owner }

// IDCardFactory は具体的な Creator(IDカード専門の工場)。
type IDCardFactory struct {
	owners []string
}

func (f *IDCardFactory) CreateProduct(owner string) Product {
	return newIDCard(owner)
}

func (f *IDCardFactory) RegisterProduct(p Product) {
	// Java版は (IDCard)product のダウンキャスト。Goでは型アサーションで対応する。
	if card, ok := p.(*IDCard); ok {
		f.owners = append(f.owners, card.Owner())
	}
}

func (f *IDCardFactory) Owners() []string { return f.owners }
main.go
package main

import "fmt"

// 実行: go run ./GoF/patterns/FactoryMethod/go
//
// main は IDCard の生成手順を知らない。framework の Create にお任せするだけ。
// 別の製品を作りたければ、別の Creator を実装して Create に渡せばよい。
func main() {
	factory := &IDCardFactory{}
	card1 := Create(factory, "User1")
	card2 := Create(factory, "User2")
	card3 := Create(factory, "User3")
	card1.Use()
	card2.Use()
	card3.Use()

	// Create 経由で登録された owner 一覧を確認。
	fmt.Println("登録済み:", factory.Owners())
}
PHP

復習: templatemethodでは、処理の流れを実際の処理から抽象化した それぞれdoActionという名前で、 validateしてエラーがあればエラーを投げて、 なければdoMainするという流れを踏んだ

index.php
<?php
ini_set("display_errors", "1");


/**
 * オブジェクトの生成過程の隠蔽と集中管理
 * 
 * 単純な生成クラスではなく
 * テンプレートメソッド的なオブジェクト準備の過程を
 * absractメソッドに定義して、
 * オブジェクト生成と準備を抽象化
 * 多様なインスタンス生成を請け負う
 * オブジェクト生成は強い依存を生むので、
 * 切り離すだけでも大きなメリットがある
 */


/**
 * 復習:
 * templatemethodでは、処理の流れを実際の処理から抽象化した
 * それぞれdoActionという名前で、
 * validateしてエラーがあればエラーを投げて、
 * なければdoMainするという流れを踏んだ
 */




abstract class ModelFactoryBase
{
  private static $instances = array();
  private function __construct()
  {
  }

  public static function getInstance($class)
  {
    if (!in_array($class, self::$instances)) {
      self::$instances[$class] = new $class;
    }
    return self::$instances[$class];
  }

  public function create($name)
  {
    $class = "${name}Model";
    if (!class_exists($class)) {
      require "$class.php";
      $obj = new $class;
      $this->setProperties($obj);
      return $obj;
    }
  }
  abstract public function setProperties($obj);
}


class ModelFactoryA extends ModelFactoryBase
{

  public function setProperties($obj)
  {
    $obj->setName("FromA");
  }
}
class ModelFactoryB extends ModelFactoryBase
{

  public function setProperties($obj)
  {
    $obj->setName("FromB");
  }
}


abstract class ModelBase
{
  protected $rows  = array();
  private $name = null;
  public function setName($name)
  {
    $this->name = $name;
  }

  public function getName()
  {
    return $this->name;
  }

  public function __construct()
  {
    $this->fillData();
  }

  public abstract function fillData();
}


class CSVModel extends ModelBase
{
  public function fillData()
  {
    ob_start();
?>
    1, name1, hoge@example.com
    2, name2, fuga@example.com
    2, name3, piyo@example.com
    <?php
    foreach (explode("\n", ob_get_clean()) as $line) {
      if (!trim($line)) {
        continue;
      }
      $row = str_getcsv($line);
      $this->rows[trim($row[0])] =  (object) array(
        "id" => trim($row[0]),
        "name" => trim($row[1]),
        "mailAddress" => trim($row[2]),
      );
    }
  }
}


class XMLModel extends ModelBase
{
  public function fillData()
  {

    ob_start();
    ?>
    <?= '<?xml version="1.0" encoding="UTF-8"?>' ?>
    <rows>
      <row id="1" name="name1" mailAddress="hoge@example.com" />
      <row id="2" name="name2" mailAddress="fizz@example.com" />
      <row id="3" name="name3" mailAddress="buzz@example.com" />
    </rows>
<?php
    foreach (simplexml_load_string(ob_get_clean())->row  as $row) {
      $obj = new stdClass();
      foreach ($row->attributes() as $key => $value) {
        $obj->key = (string) $value;
      }
      $this->rows[$obj->id] = $obj;
    }
  }
}


// foreach (array("ModelFactoryA", "ModelFactoryB") as $factoryName) {
//   $model = ModelFactoryBase::getInstance($factoryName);
//   foreach (array("XML", "CSV") as $modelName) {
//     var_dump($model);
//     echo  "<<" . $modelName . "の処理 >> <br>";
//     var_dump($model->find(1));
//     $model->delete(1);
//     $model->save((object) array(
//       "id" => 4,
//       "name" => "name4",
//       "mailAddress" => "xyzzy@example.com"
//     ));
//     var_dump($model);
//   }
// }


?>

<a href="client.php">別事例へ</a>
<br>
<a href="/docs/factoryMethod.md">説明へ</a>

TypeScript

Java版の抽象クラスはTypeScriptのabstract classで直接表現できる(Go版のような「継承→合成」の置き換えが不要)。ダウンキャストはinstanceof

実行: npx tsx GoF/patterns/FactoryMethod/typescript/main.ts

$ npx tsx GoF/patterns/FactoryMethod/typescript/main.ts
framework.ts
// Factory Method パターン: framework層 (Java版の framework パッケージに相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。
//
// Java版はFactory/Productがどちらも抽象クラスで、Factory.create()はfinal(サブクラスでの
// オーバーライド禁止)。TypeScriptにもクラスの継承と`abstract`キーワードがあるので、
// Go版のように「継承を合成に置き換える」必要はなく、Java版のクラス階層をほぼそのまま書ける。
// ただしTypeScriptにfinalメソッド修飾子は無いため、「createは骨組みなので上書きしないでね」
// というJava版の設計意図はコメントで伝えるにとどまる(protectedも実行時強制ではなく型チェックのみ)。

export abstract class Product {
  abstract use(): void;
}

export abstract class Factory {
  // Java版 Factory.create(String) の final メソッドに相当するテンプレートメソッド。
  // 「作って → 登録して → 返す」という骨組みは固定し、中身はサブクラスの
  // createProduct / registerProduct に委ねる。
  create(owner: string): Product {
    const product = this.createProduct(owner);
    this.registerProduct(product);
    return product;
  }

  protected abstract createProduct(owner: string): Product;
  protected abstract registerProduct(product: Product): void;
}
idcard.ts
// Factory Method パターン: idcard層 (Java版の idcard パッケージに相当)
// 単体では実行不可。エントリポイントは main.ts (npx tsx main.ts)。

import { Factory, Product } from "./framework";

export class IDCard extends Product {
  private owner: string;

  // Java版の IDCard(String) はパッケージプライベートで、IDCardFactory以外からnewさせない意図。
  // TypeScriptにpackage-privateは無いので通常のpublicコンストラクタになるが、
  // 「本来はFactory経由でしか作ってほしくない」という意図はコメントで残す
  // (Go版も非公開コンストラクタ関数 newIDCard で同じ意図を表現している)。
  constructor(owner: string) {
    super();
    console.log(`${owner}のカードを作ります。`);
    this.owner = owner;
  }

  use(): void {
    console.log(`${this.owner}のカードを使います。`);
  }

  getOwner(): string {
    return this.owner;
  }
}

export class IDCardFactory extends Factory {
  private owners: string[] = [];

  protected createProduct(owner: string): Product {
    return new IDCard(owner);
  }

  protected registerProduct(product: Product): void {
    // Java版の (IDCard)product ダウンキャストに相当。TypeScriptはinstanceofで安全に絞り込む。
    if (product instanceof IDCard) {
      this.owners.push(product.getOwner());
    }
  }

  getOwners(): string[] {
    return this.owners;
  }
}
main.ts
// Factory Method パターン: IDカード発行 (Java版と同じ題材)
//
// 実行: npx tsx GoF/patterns/FactoryMethod/typescript/main.ts
//
// main は IDCard の生成手順を知らない。Factory.create にお任せするだけ。
// 別の製品を作りたければ、別のFactoryサブクラスを用意して差し替えればよい。

import { Factory, Product } from "./framework";
import { IDCardFactory } from "./idcard";

function main(): void {
  const factory: Factory = new IDCardFactory();
  const card1: Product = factory.create("User1");
  const card2: Product = factory.create("User2");
  const card3: Product = factory.create("User3");
  card1.use();
  card2.use();
  card3.use();

  // Go版main.goに倣い、create経由で登録されたowner一覧も確認する。
  // (Factory型からは見えないIDCardFactory固有のメソッドなのでダウンキャストする)
  console.log("登録済み:", (factory as IDCardFactory).getOwners());
}

main();
Python

抽象クラスはabc.ABCで表現。ダウンキャストはisinstance

実行: python3 GoF/patterns/FactoryMethod/python/main.py

$ python3 GoF/patterns/FactoryMethod/python/main.py
framework.py
"""Factory Method パターン: framework層 (Java版の framework パッケージに相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。

Java版のFactory/Productはどちらも抽象クラスで、Factory.create()はfinal(サブクラスでの
オーバーライド禁止)。PythonにはJavaのinterfaceに相当する言語機能はなく、
Strategy版のPython実装と同じ方針で抽象基底クラス(ABC, abcモジュール)を使う。
finalに相当する機構も無いため、「createはテンプレートメソッドなので上書きしないでね」という
Java版の設計意図はコメントで伝えるにとどめる。createProduct/registerProductが protected な点は、
Pythonの慣習である先頭アンダースコア(_create_product等)で表現する
(先頭ダブルアンダースコアは名前マングリングでサブクラスからの実装が難しくなるため使わない)。
"""

from __future__ import annotations

from abc import ABC, abstractmethod


class Product(ABC):
    @abstractmethod
    def use(self) -> None: ...


class Factory(ABC):
    def create(self, owner: str) -> Product:
        product = self._create_product(owner)
        self._register_product(product)
        return product

    @abstractmethod
    def _create_product(self, owner: str) -> Product: ...

    @abstractmethod
    def _register_product(self, product: Product) -> None: ...
idcard.py
"""Factory Method パターン: idcard層 (Java版の idcard パッケージに相当)

単体では実行不可。エントリポイントは main.py (python3 main.py)。
"""

from __future__ import annotations

from framework import Factory, Product


class IDCard(Product):
    # Java版の IDCard(String) はパッケージプライベートで、IDCardFactory以外からnewさせない意図。
    # Pythonにアクセス修飾子は無いので、先頭アンダースコアの慣習で
    # 「直接は使わずFactory経由で」という意図をコメントとあわせて残すにとどめる。
    def __init__(self, owner: str) -> None:
        print(f"{owner}のカードを作ります。")
        self._owner = owner

    def use(self) -> None:
        print(f"{self._owner}のカードを使います。")

    def get_owner(self) -> str:
        return self._owner


class IDCardFactory(Factory):
    def __init__(self) -> None:
        self._owners: list[str] = []

    def _create_product(self, owner: str) -> Product:
        return IDCard(owner)

    def _register_product(self, product: Product) -> None:
        # Java版の (IDCard)product ダウンキャストに相当。Pythonはisinstanceで安全に絞り込む。
        if isinstance(product, IDCard):
            self._owners.append(product.get_owner())

    def get_owners(self) -> list[str]:
        return self._owners
main.py
"""Factory Method パターン: IDカード発行 (Java版と同じ題材)

実行: python3 main.py
      (もしくはリポジトリルートから python3 GoF/patterns/FactoryMethod/python/main.py)

main は IDCard の生成手順を知らない。Factory.create にお任せするだけ。
別の製品を作りたければ、別のFactoryサブクラスを用意して差し替えればよい。
"""

from __future__ import annotations

from typing import cast

from framework import Factory, Product
from idcard import IDCardFactory


def main() -> None:
    factory: Factory = IDCardFactory()
    card1: Product = factory.create("User1")
    card2: Product = factory.create("User2")
    card3: Product = factory.create("User3")
    card1.use()
    card2.use()
    card3.use()

    # Go版main.goに倣い、create経由で登録されたowner一覧も確認する。
    # (Factory型からは見えないIDCardFactory固有のメソッドなのでダウンキャストする)
    print("登録済み:", cast(IDCardFactory, factory).get_owners())


if __name__ == "__main__":
    main()

ユースケース

CSVかXMLかの方

今回の実装。上記リンクから趣旨を抜き出す

  • ある既存のCSV出力メソッドを,XMLにも対応させたいとする
  • ifでCSVかXMLか分岐させればできそう
  • しかしさらにファイル形式の対応を増やしていけば、if文が増えるばかり

ユーザがやりたいことはファイル形式が何であれ

  1. 読み込む read
  2. 表示する display

これだけ。

具体的な読み込み方法や表示方法はそれぞれのファイル形式に対応させたファクトリに任せる

システム開発

ベンダー企業とクライアントがいる

とあるクライアントはデータセンターを運営しようとして、データセンターシステムを外注しようとする。 とあるクライアントはPOSシステムを外注しようとする。

クライアントはベンダーの営業(ConcreteFactory)に 発注して納品待ち をする。だけ。どんな開発体制が取られるかは(監査を入れるでもしようとしなければ)ベンダー側に任せる

ベンダーは受注内容から データセンターシステムの受注ならその道のスペシャリストに、 POSシステムならその道のスペシャリストに開発をさせる。

ベンダーのエンジニアは共通して「開発」と「納品」ができるが、それぞれ手法は専門領域や開発スタイルから完全に異なる

料理

釣りが趣味のおっさんと持込OKレストラン

おっさんは釣った魚をレストランに渡して、調理済みの魚料理をもらう レストランは魚の種類によって、一番美味しく食べられる調理方法が可能な料理人に魚を調理させる

登場人物

  • おっさん: クライアント
  • レストラン:ファクトリ
  • 料理人:プロダクトの作り手。ファクトリに生成される
  • 料理:プロダクト