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

Abstract Factory

*抽象的な部品を組み合わせて抽象的な製品を作る*

概要

マンガでわかる Abstract Factory

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

でざぱたんで覚える Abstract Factory

ちびキャラは「アブストラクトファクトリたん」。モデルをひと目見れば最も映える一式を即断できる王都のスタイリストで、どんな提案でも「一貫性」だけは絶対に外さない。部品を別々に選ばせず「コーデ一式」で渡す=関連オブジェクト群を生成時点で一貫した組み合わせとして提供し、ちぐはぐな組み合わせを構造的に防ぐ、というAbstract Factoryの本質がキャラの信条になっている。

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

デザインパターンGoF

漫画でわかるFactory Methodより

作り方より先に使い方に着目する Abstract Factory とは違い、

同じcreateメソッドで呼び出せれば嬉しい。使い方に着目している

活かせそうなシナリオを考えてみる

HTMLフォーム部品生成

  • ログインフォーム
  • 新規登録フォーム
  • 設定フォーム

いずれもFormインタフェースを実装してcreateから生成されるようにするとか

クラス図

Abstract Factory Pattern

このサイトの実装(リンク集HTML生成)での対応関係:

classDiagram
  class Factory {
    <<abstract>>
    +getFactory(classname) Factory
    +createLink(caption, url) Link
    +createTray(caption) Tray
    +createPage(title, author) Page
  }
  class Item {
    <<abstract>>
    #caption
    +makeHTML() String
  }
  class Link {
    <<abstract>>
    #url
  }
  class Tray {
    <<abstract>>
    +add(item)
  }
  class Page {
    <<abstract>>
    #title
    #author
    +add(item)
    +output()
    +makeHTML() String
  }
  class ListFactory
  class ListLink
  class ListTray
  class ListPage
  class TableFactory
  class TableLink
  class TableTray
  class TablePage

  Item <|-- Link
  Item <|-- Tray
  Factory <|-- ListFactory
  Factory <|-- TableFactory
  Link <|-- ListLink
  Tray <|-- ListTray
  Page <|-- ListPage
  Link <|-- TableLink
  Tray <|-- TableTray
  Page <|-- TablePage
  Factory ..> Link : creates
  Factory ..> Tray : creates
  Factory ..> Page : creates
  Tray o-- Item
  Page o-- Item
Java

テーブルページかリストページか

factory/Factory.java
package factory;

public abstract class Factory {
    public static Factory getFactory(String classname) {
        Factory factory = null;
        try {
            factory = (Factory) Class.forName(classname).newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factory;
    }

    public abstract Link createLink(String caption, String url);

    public abstract Tray createTray(String caption);

    public abstract Page createPage(String title, String author);
}
factory/Item.java
package factory;

public abstract class Item {
    protected String caption;
    public Item(String caption) {
        this.caption = caption;
    }
    public abstract String makeHTML();
}
factory/Link.java
package factory;

public abstract class Link extends Item {
    protected String url;
    public Link(String caption, String url) {
        super(caption);
        this.url = url;
    }
}
factory/Tray.java
package factory;

import java.util.ArrayList;

public abstract class Tray extends Item {
    protected ArrayList<Item> tray = new ArrayList<Item>();

    public Tray(String caption) {
        super(caption);
    }

    public void add(Item item) {
        tray.add(item);
    }
}
factory/Page.java
package factory;

import java.io.*;
import java.util.ArrayList;

public abstract class Page {
    protected String title;
    protected String author;
    protected ArrayList<Item> content = new ArrayList<Item>();

    public Page(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public void add(Item item) {
        content.add(item);
    }

    public void output() {
        try {
            String filename = title + ".html";
            Writer writer = new FileWriter(filename);
            writer.write(this.makeHTML());
            writer.close();
            System.out.println(filename + " を作成しました");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public abstract String makeHTML();
}
tablefactory/TableFactory.java
package tablefactory;
import factory.*;

public class TableFactory extends Factory {
    public Link createLink(String caption, String url) {
        return new TableLink(caption, url);
    }
    public Tray createTray(String caption) {
        return new TableTray(caption);
    }
    public Page createPage(String title, String author) {
        return new TablePage(title, author);
    }
}
tablefactory/TableLink.java
package tablefactory;
import factory.*;

public class TableLink extends Link {
    public TableLink(String caption, String url) {
        super(caption, url);
    }
    public String makeHTML() {
        return "<td><a href=\"" + url + "\">" + caption + "</a></td>\n";
    }
}
tablefactory/TableTray.java
package tablefactory;

import factory.Item;
import factory.Tray;

public class TableTray extends Tray {

    public TableTray(String caption) {
        super(caption);
    }

    @Override
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<td>\n");
        buffer.append("<table width='100px'>\n<tr>\n");
        buffer.append("<td colspan='" + this.tray.size() + "'>" + this.caption + "</td>\n");
        buffer.append("</tr>\n");
        buffer.append("<tr>\n");
        for (Item item : this.tray) {
            buffer.append(item.makeHTML());
        }
        buffer.append("</tr>\n");
        buffer.append("</table>\n");
        buffer.append("</td>");

        return buffer.toString();
    }

}
tablefactory/TablePage.java
package tablefactory;
import factory.*;
import java.util.Iterator;

public class TablePage extends Page {
    public TablePage(String title, String author) {
        super(title, author);
    }
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html><head><title>" + title + "</title></head>\n");
        buffer.append("<body>\n");
        buffer.append("<h1>" + title + "</h1>\n");
        buffer.append("<table width=\"80%\" border=\"3\">\n");
        Iterator it = content.iterator();
        while (it.hasNext()) {
            Item item = (Item)it.next();
            buffer.append("<tr>" + item.makeHTML() + "</tr>");
        }
        buffer.append("</table>\n");
        buffer.append("<hr><address>" + author + "</address>");
        buffer.append("</body></html>\n");
        return buffer.toString();
    }
}
listfactory/ListFactory.java
package listfactory;

import factory.*;

/**
 * リスト工場クラス
 */
public class ListFactory extends Factory {

  /**
   * リンクを作成する
   * 
   * @param caption 見出し
   * @param url     遷移先
   */
  @Override
  public Link createLink(String caption, String url) {
    return new ListLink(caption, url);
  }

  /**
   * お盆を作成する
   * 
   * @param caption 見出し
   */
  @Override
  public Tray createTray(String caption) {
    return new ListTray(caption);
  }

  /**
   * ページを作成する
   * 
   * @param title  タイトル
   * @param author 著者
   */

  @Override
  public Page createPage(String title, String author) {
    return new ListPage(title, author);
  }

}
listfactory/ListLink.java
package listfactory;

import factory.*;

/**
 * リストページ内のリンクアイテム
 */
public class ListLink extends Link {
  public ListLink(String caption, String url) {
    super(caption, url);
  }

  /**
   * リンクタグ実物を作成して返す
   * 
   * @return リンクタグ
   */

  @Override
  public String makeHTML() {
    return "<li><a href=\"" + this.url + "\">" + this.caption + "</a></li>\n";
  }

}
listfactory/ListTray.java
package listfactory;

import factory.*;

/**
 * リスト内のお盆アイテムクラス
 */
public class ListTray extends Tray {

  /**
   * コンストラクタ
   */
  public ListTray(String caption) {
    super(caption);
  }

  /**
   * タグを作成する<br>
   * liのなかにulを作成して、その中に複数のliを置く
   * 
   * @return リストお盆
   */
  public String makeHTML() {
    StringBuffer buffer = new StringBuffer();
    buffer.append("<li>\n");
    buffer.append(this.caption + "\n");
    buffer.append("<ul>\n");
    for (Item item : this.tray) {
      buffer.append(item.makeHTML());
    }
    buffer.append("</ul>\n");
    buffer.append("</li>\n");

    return buffer.toString();
  }

}
listfactory/ListPage.java
package listfactory;

import factory.*;

public class ListPage extends Page {

  /**
   * コンストラクタ
   * 
   * @param title  題名
   * @param author 著者
   */
  public ListPage(String title, String author) {
    super(title, author);
  }

  @Override
  public String makeHTML() {
    StringBuffer buffer = new StringBuffer();
    buffer.append("<html><head><title>" + this.title + "</title></head>\n");
    buffer.append("<body>\n");
    buffer.append("<h1>" + this.title + "</h1>\n");
    buffer.append("<ul>\n");
    for (Item item : this.content) {
      buffer.append(item.makeHTML());
    }
    buffer.append("</ul>\n");
    buffer.append("<hr><address>" + this.author + "</address>");
    buffer.append("</body></html>\n");
    return buffer.toString();
  }

}
Main.java
import factory.*;

public class Main {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: java Main class.name.of.ConcreteFactory");
            System.out.println("Example 1: java Main listfactory.ListFactory");
            System.out.println("Example 2: java Main tablefactory.TableFactory");
            System.exit(0);
        }
        Factory factory = Factory.getFactory(args[0]);

        Link asahi = factory.createLink("helloworld", "http://www.asahi.com/");
        Link yomiuri = factory.createLink("helloworld", "http://www.yomiuri.co.jp/");

        Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
        Link jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/");
        Link excite = factory.createLink("Excite", "http://www.excite.com/");
        Link google = factory.createLink("Google", "http://www.google.com/");

        Tray traynews = factory.createTray("helloworld");
        traynews.add(asahi);
        traynews.add(yomiuri);

        Tray trayyahoo = factory.createTray("Yahoo!");
        trayyahoo.add(us_yahoo);
        trayyahoo.add(jp_yahoo);

        Tray traysearch = factory.createTray("helloworld");
        traysearch.add(trayyahoo);
        traysearch.add(excite);
        traysearch.add(google);

        Page page = factory.createPage("LinkPage", "hello worlds");
        page.add(traynews);
        page.add(traysearch);
        page.output();
    }
}
Go

Java版と同じ「リンク集HTML生成」を移植。抽象継承が無いGoでは、抽象クラス群を interface に、Class.forName の動的ロードを getFactory の switch に置き換える。list/table どちらのファミリを渡しても main の組み立てコードは不変。

実行: go run ./GoF/patterns/AbstractFactory/go [list|table]

$ go run ./GoF/patterns/AbstractFactory/go
abstractfactory.go
package main

import "fmt"

// ---- 抽象層(Java版の factory パッケージに相当)----
//
// AbstractFactory の肝は「関連する部品の“ファミリ”をまるごと差し替える」こと。
// クライアントは下の interface だけを見て組み立てるので、
// list ファミリ / table ファミリのどちらを渡されても一切コードが変わらない。

// Item は HTML の部品。Link と Tray の共通の抽象(Java の抽象クラス Item)。
type Item interface {
	MakeHTML() string
}

// Link は単独のリンク部品。
// この例では Item に公開メソッドを足さないので、Item を埋め込むだけ。
type Link interface {
	Item
}

// Tray は複数の Item をまとめる入れ物(お盆)。Tray 自身も Item になれる(入れ子可)。
type Tray interface {
	Item
	Add(item Item)
}

// Page は 1 枚の HTML ページ。部品そのものではないので Item ではない。
type Page interface {
	Add(item Item)
	MakeHTML() string
	Output()
}

// Factory は抽象工場。どの具体ファミリも同じ顔(このメソッド群)を見せる。
type Factory interface {
	CreateLink(caption, url string) Link
	CreateTray(caption string) Tray
	CreatePage(title, author string) Page
}

// getFactory は名前から具体工場を選ぶ。
// Java版は Class.forName(classname) のリフレクションで動的にロードするが、
// Go に動的クラスロードは無いので、明示的な switch(レジストリ)で対応する。
// ← ここが Java と Go の設計上の一番の差。
func getFactory(name string) (Factory, error) {
	switch name {
	case "list":
		return &ListFactory{}, nil
	case "table":
		return &TableFactory{}, nil
	default:
		return nil, fmt.Errorf("unknown factory: %q (list または table)", name)
	}
}
list.go
package main

import (
	"fmt"
	"strings"
)

// ---- list ファミリ(Java版の listfactory パッケージに相当)----
// <ul>/<li> でリンク集を組む。

// ListFactory は list ファミリの具体工場。
type ListFactory struct{}

func (f *ListFactory) CreateLink(caption, url string) Link {
	return &listLink{caption: caption, url: url}
}
func (f *ListFactory) CreateTray(caption string) Tray {
	return &listTray{caption: caption}
}
func (f *ListFactory) CreatePage(title, author string) Page {
	return &listPage{title: title, author: author}
}

type listLink struct {
	caption string
	url     string
}

func (l *listLink) MakeHTML() string {
	return fmt.Sprintf("<li><a href=\"%s\">%s</a></li>\n", l.url, l.caption)
}

type listTray struct {
	caption string
	items   []Item
}

func (t *listTray) Add(item Item) { t.items = append(t.items, item) }

func (t *listTray) MakeHTML() string {
	var b strings.Builder
	b.WriteString("<li>\n")
	b.WriteString(t.caption + "\n")
	b.WriteString("<ul>\n")
	for _, item := range t.items {
		b.WriteString(item.MakeHTML())
	}
	b.WriteString("</ul>\n")
	b.WriteString("</li>\n")
	return b.String()
}

type listPage struct {
	title   string
	author  string
	content []Item
}

func (p *listPage) Add(item Item) { p.content = append(p.content, item) }

func (p *listPage) MakeHTML() string {
	var b strings.Builder
	fmt.Fprintf(&b, "<html><head><title>%s</title></head>\n", p.title)
	b.WriteString("<body>\n")
	fmt.Fprintf(&b, "<h1>%s</h1>\n", p.title)
	b.WriteString("<ul>\n")
	for _, item := range p.content {
		b.WriteString(item.MakeHTML())
	}
	b.WriteString("</ul>\n")
	fmt.Fprintf(&b, "<hr><address>%s</address>", p.author)
	b.WriteString("</body></html>\n")
	return b.String()
}

// Output は Java版ではファイルに書き出すが、ここでは runnable なデモにするため標準出力へ。
func (p *listPage) Output() { fmt.Print(p.MakeHTML()) }
table.go
package main

import (
	"fmt"
	"strings"
)

// ---- table ファミリ(Java版の tablefactory パッケージに相当)----
// <table> でリンク集を組む。list ファミリと「同じ Factory interface」を実装する。

// TableFactory は table ファミリの具体工場。
type TableFactory struct{}

func (f *TableFactory) CreateLink(caption, url string) Link {
	return &tableLink{caption: caption, url: url}
}
func (f *TableFactory) CreateTray(caption string) Tray {
	return &tableTray{caption: caption}
}
func (f *TableFactory) CreatePage(title, author string) Page {
	return &tablePage{title: title, author: author}
}

type tableLink struct {
	caption string
	url     string
}

func (l *tableLink) MakeHTML() string {
	return fmt.Sprintf("<td><a href=\"%s\">%s</a></td>\n", l.url, l.caption)
}

type tableTray struct {
	caption string
	items   []Item
}

func (t *tableTray) Add(item Item) { t.items = append(t.items, item) }

func (t *tableTray) MakeHTML() string {
	var b strings.Builder
	b.WriteString("<td>\n")
	b.WriteString("<table width='100px'>\n<tr>\n")
	fmt.Fprintf(&b, "<td colspan='%d'>%s</td>\n", len(t.items), t.caption)
	b.WriteString("</tr>\n<tr>\n")
	for _, item := range t.items {
		b.WriteString(item.MakeHTML())
	}
	b.WriteString("</tr>\n</table>\n</td>")
	return b.String()
}

type tablePage struct {
	title   string
	author  string
	content []Item
}

func (p *tablePage) Add(item Item) { p.content = append(p.content, item) }

func (p *tablePage) MakeHTML() string {
	var b strings.Builder
	fmt.Fprintf(&b, "<html><head><title>%s</title></head>\n", p.title)
	b.WriteString("<body>\n")
	fmt.Fprintf(&b, "<h1>%s</h1>\n", p.title)
	b.WriteString("<table width=\"80%\" border=\"3\">\n")
	for _, item := range p.content {
		fmt.Fprintf(&b, "<tr>%s</tr>", item.MakeHTML())
	}
	b.WriteString("</table>\n")
	fmt.Fprintf(&b, "<hr><address>%s</address>", p.author)
	b.WriteString("</body></html>\n")
	return b.String()
}

func (p *tablePage) Output() { fmt.Print(p.MakeHTML()) }
main.go
package main

import (
	"fmt"
	"os"
)

// 実行:
//
//	go run ./GoF/patterns/AbstractFactory/go        # デフォルト: list ファミリ
//	go run ./GoF/patterns/AbstractFactory/go list   # <ul>/<li> でHTML生成
//	go run ./GoF/patterns/AbstractFactory/go table  # <table> でHTML生成
//
// 注目点: 下の組み立てコード(クライアント)は、
// list でも table でも 1 行も変わらない。差し替えているのは getFactory の戻り値だけ。
func main() {
	kind := "list"
	if len(os.Args) == 2 {
		kind = os.Args[1]
	}
	factory, err := getFactory(kind)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}

	asahi := factory.CreateLink("朝日新聞", "http://www.asahi.com/")
	yomiuri := factory.CreateLink("読売新聞", "http://www.yomiuri.co.jp/")
	usYahoo := factory.CreateLink("Yahoo!", "http://www.yahoo.com/")
	jpYahoo := factory.CreateLink("Yahoo!Japan", "http://www.yahoo.co.jp/")
	excite := factory.CreateLink("Excite", "http://www.excite.com/")
	google := factory.CreateLink("Google", "http://www.google.com/")

	trayNews := factory.CreateTray("新聞")
	trayNews.Add(asahi)
	trayNews.Add(yomiuri)

	trayYahoo := factory.CreateTray("Yahoo!")
	trayYahoo.Add(usYahoo)
	trayYahoo.Add(jpYahoo)

	traySearch := factory.CreateTray("サーチエンジン")
	traySearch.Add(trayYahoo)
	traySearch.Add(excite)
	traySearch.Add(google)

	page := factory.CreatePage("LinkPage", "アキラ")
	page.Add(trayNews)
	page.Add(traySearch)
	page.Output()
}
PHP
index.php
<?php
ini_set("display_errors", "1");

/**
 * 単にnewするだけでなく
 * プロパティに依存オブジェクトを設定して
 * 初めて利用可能になるようなオブジェクトにおける
 * プロパティ設定の一貫性を保つために使う
 * 
 */


/**
 * 実ファクトリを用いて
 * 一貫性のあるプロパティを設定することを目的としたパターン
 * 依存性注入によってお株を奪われつつあるらしい
 *
 */


/**
 *  アブストファクトファクトリ
 * 「作る」
 * 何を作るのかは子供による
 * 
 */
interface FashionFactory
{
  public function createTops();
  public function createBottoms();
  public function createCap();
}


/**
 * ファクトリを使ってプロパティを設定される対象
 * 服
 */
class Fashion
{
  private $tops = null;
  private $bottoms = null;
  private $cap = null;

  public function setTops($tops)
  {
    $this->tops = $tops;
  }
  public function setBottoms($bottoms)
  {
    $this->bottoms = $bottoms;
  }

  public function setCap($cap)
  {
    $this->cap = $cap;
  }



  public function getTops()
  {
    return $this->tops;
  }

  public function getBottoms()
  {
    return $this->bottoms;
  }

  public function getCap()
  {
    return $this->cap;
  }
}

/**
 * 実ファクトリその1
 * 和服工場
 */


class JapaneseFashionFactory implements FashionFactory
{
  private static $instance = null;
  public function __construct()
  {
  }

  // singleton?
  public static function getInstance()
  {
    if (self::$instance == null) {
      // 自分をメンバに持つ
      self::$instance = new JapaneseFashionFactory();
    }
    return self::$instance;
  }

  public function createTops()
  {
    return "長着";
  }
  public function createBottoms()
  {
    return "袴";
  }
  public function createCap()
  {
    return "三度笠";
  }
}

/**
 * 実ファクトリ2
 * 洋服工場
 */




class ForeignFashionFactory  implements FashionFactory
{
  private static $instance = null;
  public function __construct()
  {
  }

  public static function getInstance()
  {
    if (self::$instance == null) {
      // 自分をメンバに持つ
      self::$instance = new ForeignFashionFactory();
    }
    return self::$instance;
  }

  public function createTops()
  {
    return "ジャケット";
  }
  public function createBottoms()
  {
    return "ジーンズ";
  }

  public function createCap()
  {
    return "キャップ";
  }
}


// 和服工場
$jpFactory = JapaneseFashionFactory::getInstance();

// 洋服
$foreignFactory = ForeignFashionFactory::getInstance();




// どちらが作ろうがcreate****という呼び出しになっているのがミソ
// 服に和服工場のトップスとボトムズを設定
$jpFashion = new Fashion();
$jpFashion->setTops($jpFactory->createTops());
$jpFashion->setBottoms($jpFactory->createBottoms());
$jpFashion->setCap($jpFactory->createCap());

$foreignFashion = new Fashion();
$foreignFashion->setTops($foreignFactory->createTops());
$foreignFashion->setBottoms($foreignFactory->createBottoms());
$foreignFashion->setCap($foreignFactory->createCap());

var_dump($jpFashion);
var_dump($foreignFashion);
TypeScript

Java版の抽象クラス群は interface で素直に表現できる。Go版と同じく Class.forName の動的ロードは無いので、getFactory の分岐で置き換える。

実行: npx tsx GoF/patterns/AbstractFactory/typescript/main.ts [list|table]

$ npx tsx GoF/patterns/AbstractFactory/typescript/main.ts
factory.ts
// ---- 抽象層(Java版の factory パッケージ / Go版の abstractfactory.go に相当)----
//
// AbstractFactory の肝は「関連する部品の"ファミリ"をまるごと差し替える」こと。
// クライアント(main.ts)は下の interface だけを見て組み立てるので、
// list ファミリ / table ファミリのどちらを渡されても一切コードが変わらない。
//
// TypeScriptの`interface`はJavaの`interface`にかなり近い書き味で使える。
// 実装クラス側は Go版のような暗黙実装ではなく、Java同様 `implements` を明示する。

export interface Item {
  makeHTML(): string;
}

// Link は単独のリンク部品。この例では Item に公開メソッドを足さないので、Item をそのまま継承する。
export interface Link extends Item {}

// Tray は複数の Item をまとめる入れ物(お盆)。Tray 自身も Item になれる(入れ子可)。
export interface Tray extends Item {
  add(item: Item): void;
}

// Page は1枚のHTMLページ。部品そのものではないので Item ではない。
export interface Page {
  add(item: Item): void;
  makeHTML(): string;
  output(): void;
}

// Factory は抽象工場。どの具体ファミリも同じ顔(このメソッド群)を見せる。
export interface Factory {
  createLink(caption: string, url: string): Link;
  createTray(caption: string): Tray;
  createPage(title: string, author: string): Page;
}

// list.ts / table.ts は型としてこのファイルを `import type` するだけ(実行時の依存なし)なので、
// ここから ListFactory / TableFactory を実体importしても循環参照は起きない。
import { ListFactory } from "./list";
import { TableFactory } from "./table";

// getFactory: Java版は Class.forName(classname) のリフレクションで動的にロードするが、
// TypeScriptにも動的importはあるものの非同期かつ型安全でないため、
// Go版と同じく明示レジストリ(switch)で対応する。← ここがJavaとの設計上の一番の差。
export function getFactory(name: string): Factory {
  switch (name) {
    case "list":
      return new ListFactory();
    case "table":
      return new TableFactory();
    default:
      throw new Error(`unknown factory: "${name}" (list または table)`);
  }
}
list.ts
// ---- list ファミリ(Java版の listfactory パッケージ / Go版の list.go に相当)----
// <ul>/<li> でリンク集を組む。table ファミリ(table.ts)と「同じ Factory interface」を実装する。
//
// Factory/Item/Link/Tray/Page は型としてのみ使うので `import type` にする。
// これにより factory.ts → list.ts(実体import)と list.ts → factory.ts(型のみ)は
// コンパイル後に実行時の循環参照を生まない。

import type { Factory, Item, Link, Page, Tray } from "./factory";

export class ListFactory implements Factory {
  createLink(caption: string, url: string): Link {
    return new ListLink(caption, url);
  }
  createTray(caption: string): Tray {
    return new ListTray(caption);
  }
  createPage(title: string, author: string): Page {
    return new ListPage(title, author);
  }
}

// 具体部品クラスは export しない。Go版で listLink / listTray / listPage を
// 小文字始まり(パッケージ内非公開)にしているのと同じ意図で、外部から直接newさせない。
class ListLink implements Link {
  constructor(private readonly caption: string, private readonly url: string) {}

  makeHTML(): string {
    return `<li><a href="${this.url}">${this.caption}</a></li>\n`;
  }
}

class ListTray implements Tray {
  private readonly items: Item[] = [];

  constructor(private readonly caption: string) {}

  add(item: Item): void {
    this.items.push(item);
  }

  makeHTML(): string {
    let html = "<li>\n";
    html += `${this.caption}\n`;
    html += "<ul>\n";
    for (const item of this.items) {
      html += item.makeHTML();
    }
    html += "</ul>\n";
    html += "</li>\n";
    return html;
  }
}

class ListPage implements Page {
  private readonly content: Item[] = [];

  constructor(private readonly title: string, private readonly author: string) {}

  add(item: Item): void {
    this.content.push(item);
  }

  makeHTML(): string {
    let html = `<html><head><title>${this.title}</title></head>\n`;
    html += "<body>\n";
    html += `<h1>${this.title}</h1>\n`;
    html += "<ul>\n";
    for (const item of this.content) {
      html += item.makeHTML();
    }
    html += "</ul>\n";
    html += `<hr><address>${this.author}</address>`;
    html += "</body></html>\n";
    return html;
  }

  // Java版はファイルに書き出すが、Go版に倣ってrunnableなデモにするため標準出力へ書く。
  output(): void {
    process.stdout.write(this.makeHTML());
  }
}
table.ts
// ---- table ファミリ(Java版の tablefactory パッケージ / Go版の table.go に相当)----
// <table> でリンク集を組む。list ファミリ(list.ts)と「同じ Factory interface」を実装する。

import type { Factory, Item, Link, Page, Tray } from "./factory";

export class TableFactory implements Factory {
  createLink(caption: string, url: string): Link {
    return new TableLink(caption, url);
  }
  createTray(caption: string): Tray {
    return new TableTray(caption);
  }
  createPage(title: string, author: string): Page {
    return new TablePage(title, author);
  }
}

// list.ts 同様、具体部品クラスは export しない(外部から直接newさせない)。
class TableLink implements Link {
  constructor(private readonly caption: string, private readonly url: string) {}

  makeHTML(): string {
    return `<td><a href="${this.url}">${this.caption}</a></td>\n`;
  }
}

class TableTray implements Tray {
  private readonly items: Item[] = [];

  constructor(private readonly caption: string) {}

  add(item: Item): void {
    this.items.push(item);
  }

  makeHTML(): string {
    let html = "<td>\n";
    html += "<table width='100px'>\n<tr>\n";
    html += `<td colspan='${this.items.length}'>${this.caption}</td>\n`;
    html += "</tr>\n<tr>\n";
    for (const item of this.items) {
      html += item.makeHTML();
    }
    html += "</tr>\n</table>\n</td>";
    return html;
  }
}

class TablePage implements Page {
  private readonly content: Item[] = [];

  constructor(private readonly title: string, private readonly author: string) {}

  add(item: Item): void {
    this.content.push(item);
  }

  makeHTML(): string {
    let html = `<html><head><title>${this.title}</title></head>\n`;
    html += "<body>\n";
    html += `<h1>${this.title}</h1>\n`;
    html += '<table width="80%" border="3">\n';
    for (const item of this.content) {
      html += `<tr>${item.makeHTML()}</tr>`;
    }
    html += "</table>\n";
    html += `<hr><address>${this.author}</address>`;
    html += "</body></html>\n";
    return html;
  }

  output(): void {
    process.stdout.write(this.makeHTML());
  }
}
main.ts
// AbstractFactory パターン: リンク集ページ生成 (Java版と同じ題材)
//
// 実行:
//   npx tsx main.ts            # デフォルト: list ファミリ
//   npx tsx main.ts list       # <ul>/<li> でHTML生成
//   npx tsx main.ts table      # <table> でHTML生成
//
// 注目点: 下の組み立てコード(クライアント)は、list でも table でも1行も変わらない。
// 差し替えているのは getFactory の戻り値だけ。

import { getFactory } from "./factory";

function main(): void {
  const kind = process.argv[2] ?? "list";

  let factory;
  try {
    factory = getFactory(kind);
  } catch (e) {
    console.error(e instanceof Error ? e.message : e);
    process.exit(1);
  }

  const asahi = factory.createLink("朝日新聞", "http://www.asahi.com/");
  const yomiuri = factory.createLink("読売新聞", "http://www.yomiuri.co.jp/");
  const usYahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
  const jpYahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/");
  const excite = factory.createLink("Excite", "http://www.excite.com/");
  const google = factory.createLink("Google", "http://www.google.com/");

  const trayNews = factory.createTray("新聞");
  trayNews.add(asahi);
  trayNews.add(yomiuri);

  const trayYahoo = factory.createTray("Yahoo!");
  trayYahoo.add(usYahoo);
  trayYahoo.add(jpYahoo);

  const traySearch = factory.createTray("サーチエンジン");
  traySearch.add(trayYahoo);
  traySearch.add(excite);
  traySearch.add(google);

  const page = factory.createPage("LinkPage", "アキラ");
  page.add(trayNews);
  page.add(traySearch);
  page.output();
}

main();
Python

抽象クラス群は abc.ABC で表現。Class.forName 相当が無いのはGo/TSと同じ事情で、get_factory の分岐で解決する。

実行: python3 GoF/patterns/AbstractFactory/python/main.py [list|table]

$ python3 GoF/patterns/AbstractFactory/python/main.py
factory.py
"""抽象層(Java版の factory パッケージ / Go版の abstractfactory.go に相当)

AbstractFactory の肝は「関連する部品の"ファミリ"をまるごと差し替える」こと。
クライアント(main.py)は下の抽象クラスだけを見て組み立てるので、
list ファミリ / table ファミリのどちらを渡されても一切コードが変わらない。

PythonにはJavaのinterfaceに相当する言語機能がないため、Strategy版と同じく
抽象基底クラス(ABC, abcモジュール)で表現する。Goの暗黙実装とは対照的に、
Java/Pythonはどちらも継承(Java: implements、Python: サブクラス化)を明示する必要がある。
"""

from __future__ import annotations

from abc import ABC, abstractmethod


class Item(ABC):
    def __init__(self, caption: str) -> None:
        self._caption = caption

    @abstractmethod
    def make_html(self) -> str: ...


# Link は単独のリンク部品。この例では Item に公開メソッドを足さないので、そのまま継承するだけ。
class Link(Item):
    def __init__(self, caption: str, url: str) -> None:
        super().__init__(caption)
        self._url = url


# Tray は複数の Item をまとめる入れ物(お盆)。Tray自身も Item になれる(入れ子可)。
class Tray(Item):
    def __init__(self, caption: str) -> None:
        super().__init__(caption)
        self._items: list[Item] = []

    def add(self, item: Item) -> None:
        self._items.append(item)


# Page は1枚のHTMLページ。部品そのものではないので Item ではない。
class Page(ABC):
    def __init__(self, title: str, author: str) -> None:
        self._title = title
        self._author = author
        self._content: list[Item] = []

    def add(self, item: Item) -> None:
        self._content.append(item)

    def output(self) -> None:
        # Java版はファイルに書き出すが、Go/TS版に倣ってrunnableなデモにするため標準出力へ書く。
        print(self.make_html(), end="")

    @abstractmethod
    def make_html(self) -> str: ...


# Factory は抽象工場。どの具体ファミリも同じ顔(このメソッド群)を見せる。
class Factory(ABC):
    @abstractmethod
    def create_link(self, caption: str, url: str) -> Link: ...

    @abstractmethod
    def create_tray(self, caption: str) -> Tray: ...

    @abstractmethod
    def create_page(self, title: str, author: str) -> Page: ...


def get_factory(name: str) -> Factory:
    """Java版は Class.forName(classname) のリフレクションで動的にロードするが、
    Go/TS版と同じく、ここでも明示レジストリ(if/elif)で対応する。

    list_factory / table_factory はこのモジュールの Item/Link/Tray/Page/Factory に
    依存するので、モジュール先頭でimportすると循環importになる。呼び出し時(関数内)で
    importすれば、その時点で factory モジュールの初期化は完了しているので問題ない。
    """
    from list_factory import ListFactory
    from table_factory import TableFactory

    if name == "list":
        return ListFactory()
    if name == "table":
        return TableFactory()
    raise ValueError(f'unknown factory: "{name}" (list または table)')
list_factory.py
"""list ファミリ(Java版の listfactory パッケージ / Go版の list.go / TS版の list.ts に相当)

<ul>/<li> でリンク集を組む。table ファミリ(table_factory.py)と
「同じ Factory 抽象クラス」を継承する。
"""

from __future__ import annotations

from factory import Factory, Item, Link, Page, Tray


class ListFactory(Factory):
    def create_link(self, caption: str, url: str) -> Link:
        return _ListLink(caption, url)

    def create_tray(self, caption: str) -> Tray:
        return _ListTray(caption)

    def create_page(self, title: str, author: str) -> Page:
        return _ListPage(title, author)


# 具体部品クラスは先頭に `_` を付けて非公開にする。Go版で listLink / listTray / listPage を
# 小文字始まり(パッケージ内非公開)にしているのと同じ意図で、外部から直接生成させない。
class _ListLink(Link):
    def make_html(self) -> str:
        return f'<li><a href="{self._url}">{self._caption}</a></li>\n'


class _ListTray(Tray):
    def make_html(self) -> str:
        html = "<li>\n"
        html += f"{self._caption}\n"
        html += "<ul>\n"
        for item in self._items:
            html += item.make_html()
        html += "</ul>\n"
        html += "</li>\n"
        return html


class _ListPage(Page):
    def make_html(self) -> str:
        html = f"<html><head><title>{self._title}</title></head>\n"
        html += "<body>\n"
        html += f"<h1>{self._title}</h1>\n"
        html += "<ul>\n"
        for item in self._content:
            html += item.make_html()
        html += "</ul>\n"
        html += f"<hr><address>{self._author}</address>"
        html += "</body></html>\n"
        return html
table_factory.py
"""table ファミリ(Java版の tablefactory パッケージ / Go版の table.go / TS版の table.ts に相当)

<table> でリンク集を組む。list ファミリ(list_factory.py)と
「同じ Factory 抽象クラス」を継承する。
"""

from __future__ import annotations

from factory import Factory, Item, Link, Page, Tray


class TableFactory(Factory):
    def create_link(self, caption: str, url: str) -> Link:
        return _TableLink(caption, url)

    def create_tray(self, caption: str) -> Tray:
        return _TableTray(caption)

    def create_page(self, title: str, author: str) -> Page:
        return _TablePage(title, author)


# list_factory.py 同様、具体部品クラスは非公開(先頭 `_`)にする。
class _TableLink(Link):
    def make_html(self) -> str:
        return f'<td><a href="{self._url}">{self._caption}</a></td>\n'


class _TableTray(Tray):
    def make_html(self) -> str:
        html = "<td>\n"
        html += "<table width='100px'>\n<tr>\n"
        html += f"<td colspan='{len(self._items)}'>{self._caption}</td>\n"
        html += "</tr>\n<tr>\n"
        for item in self._items:
            html += item.make_html()
        html += "</tr>\n</table>\n</td>"
        return html


class _TablePage(Page):
    def make_html(self) -> str:
        html = f"<html><head><title>{self._title}</title></head>\n"
        html += "<body>\n"
        html += f"<h1>{self._title}</h1>\n"
        html += '<table width="80%" border="3">\n'
        for item in self._content:
            html += f"<tr>{item.make_html()}</tr>"
        html += "</table>\n"
        html += f"<hr><address>{self._author}</address>"
        html += "</body></html>\n"
        return html
main.py
"""AbstractFactory パターン: リンク集ページ生成 (Java版と同じ題材)

実行:
    python3 main.py            # デフォルト: list ファミリ
    python3 main.py list       # <ul>/<li> でHTML生成
    python3 main.py table      # <table> でHTML生成

注目点: 下の組み立てコード(クライアント)は、list でも table でも1行も変わらない。
差し替えているのは get_factory の戻り値だけ。
"""

from __future__ import annotations

import sys

from factory import get_factory


def main() -> None:
    kind = sys.argv[1] if len(sys.argv) == 2 else "list"
    try:
        factory = get_factory(kind)
    except ValueError as e:
        print(e, file=sys.stderr)
        sys.exit(1)

    asahi = factory.create_link("朝日新聞", "http://www.asahi.com/")
    yomiuri = factory.create_link("読売新聞", "http://www.yomiuri.co.jp/")
    us_yahoo = factory.create_link("Yahoo!", "http://www.yahoo.com/")
    jp_yahoo = factory.create_link("Yahoo!Japan", "http://www.yahoo.co.jp/")
    excite = factory.create_link("Excite", "http://www.excite.com/")
    google = factory.create_link("Google", "http://www.google.com/")

    tray_news = factory.create_tray("新聞")
    tray_news.add(asahi)
    tray_news.add(yomiuri)

    tray_yahoo = factory.create_tray("Yahoo!")
    tray_yahoo.add(us_yahoo)
    tray_yahoo.add(jp_yahoo)

    tray_search = factory.create_tray("サーチエンジン")
    tray_search.add(tray_yahoo)
    tray_search.add(excite)
    tray_search.add(google)

    page = factory.create_page("LinkPage", "アキラ")
    page.add(tray_news)
    page.add(tray_search)
    page.output()


if __name__ == "__main__":
    main()