A collection of notes and resources
GoF(Gang of Four: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)が1994年の著書 “Design Patterns: Elements of Reusable Object-Oriented Software” で示した、オブジェクト指向設計における 23種類の再利用可能な設計パターン。目的による3分類で整理されている。
| カテゴリ | 概要 | 主なパターン数 |
|---|---|---|
| 生成(Creational) | オブジェクトの生成方法を抽象化し、生成と利用を分離する | 5 |
| 構造(Structural) | クラスやオブジェクトを組み合わせて、より大きな構造を作る | 7 |
| 振る舞い(Behavioral) | オブジェクト間の責務分担・通信方法を整理する | 11 |
本ノートでは上記の試験頻出パターンを中心に、目的・構造図(クラス図)・TypeScriptサンプルコード・ 試験で問われやすいポイントをまとめる。
オブジェクトの「生成方法」を、利用する側のコードから分離することが目的。 具体的なクラス名に依存せず、インターフェースや抽象クラスを通じてオブジェクトを生成できるようにする。
目的: クラスのインスタンスがただ1つだけ存在することを保証し、そのインスタンスへの グローバルなアクセス手段を提供する。
構造:
classDiagram
class Singleton {
-static instance: Singleton
-Singleton()
+static getInstance(): Singleton
+operation(): void
}
note for Singleton "instanceは自分自身のクラス型\nコンストラクタはprivate"
TypeScript例:
class Singleton {
private static instance: Singleton;
// コンストラクタをprivateにして外部からのnewを禁止
private constructor(private value: number = 0) {}
public static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
public increment(): number {
return ++this.value;
}
}
const a = Singleton.getInstance();
const b = Singleton.getInstance();
console.log(a === b); // true: 同一インスタンス
試験で問われやすいポイント:
目的: オブジェクトの生成処理をサブクラスに委ね、どのクラスをインスタンス化するかを サブクラス側で決定できるようにする。
構造:
classDiagram
class Creator {
+factoryMethod(): Product
+operation(): void
}
class ConcreteCreatorA {
+factoryMethod(): Product
}
class ConcreteCreatorB {
+factoryMethod(): Product
}
class Product {
<<interface>>
}
class ConcreteProductA
class ConcreteProductB
Creator <|-- ConcreteCreatorA
Creator <|-- ConcreteCreatorB
Product <|.. ConcreteProductA
Product <|.. ConcreteProductB
ConcreteCreatorA ..> ConcreteProductA : creates
ConcreteCreatorB ..> ConcreteProductB : creates
TypeScript例:
interface NotificationSender {
send(message: string): void;
}
class EmailNotification implements NotificationSender {
send(message: string): void {
console.log(`Email送信: ${message}`);
}
}
class SmsNotification implements NotificationSender {
send(message: string): void {
console.log(`SMS送信: ${message}`);
}
}
abstract class NotifierFactory {
// サブクラスが生成方法を決定する
abstract createNotification(): NotificationSender;
notify(message: string): void {
const notification = this.createNotification();
notification.send(message);
}
}
class EmailNotifierFactory extends NotifierFactory {
createNotification(): NotificationSender {
return new EmailNotification();
}
}
class SmsNotifierFactory extends NotifierFactory {
createNotification(): NotificationSender {
return new SmsNotification();
}
}
const factory: NotifierFactory = new SmsNotifierFactory();
factory.notify("会議は15時からです"); // SMS送信: 会議は15時からです
試験で問われやすいポイント:
目的: 互いに関連性・依存性のある複数のオブジェクト群(製品ファミリー)を、 具体的なクラスを指定せずに生成するためのインターフェースを提供する。
構造:
classDiagram
class AbstractFactory {
<<interface>>
+createButton(): Button
+createCheckbox(): Checkbox
}
class WindowsFactory {
+createButton(): Button
+createCheckbox(): Checkbox
}
class MacFactory {
+createButton(): Button
+createCheckbox(): Checkbox
}
class Button {
<<interface>>
}
class Checkbox {
<<interface>>
}
class WindowsButton
class MacButton
class WindowsCheckbox
class MacCheckbox
AbstractFactory <|.. WindowsFactory
AbstractFactory <|.. MacFactory
Button <|.. WindowsButton
Button <|.. MacButton
Checkbox <|.. WindowsCheckbox
Checkbox <|.. MacCheckbox
WindowsFactory ..> WindowsButton
WindowsFactory ..> WindowsCheckbox
MacFactory ..> MacButton
MacFactory ..> MacCheckbox
TypeScript例:
interface Button {
render(): string;
}
interface Checkbox {
render(): string;
}
class WindowsButton implements Button {
render() { return "[Windows Button]"; }
}
class WindowsCheckbox implements Checkbox {
render() { return "[Windows Checkbox]"; }
}
class MacButton implements Button {
render() { return "(Mac Button)"; }
}
class MacCheckbox implements Checkbox {
render() { return "(Mac Checkbox)"; }
}
interface UiFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
class WindowsUiFactory implements UiFactory {
createButton(): Button { return new WindowsButton(); }
createCheckbox(): Checkbox { return new WindowsCheckbox(); }
}
class MacUiFactory implements UiFactory {
createButton(): Button { return new MacButton(); }
createCheckbox(): Checkbox { return new MacCheckbox(); }
}
function renderUi(factory: UiFactory): void {
console.log(factory.createButton().render());
console.log(factory.createCheckbox().render());
}
renderUi(new MacUiFactory());
// (Mac Button)
// (Mac Checkbox)
試験で問われやすいポイント:
目的: 複雑なオブジェクトの生成過程(組み立て手順)と表現(最終的な構造)を分離し、 同じ生成過程で異なる表現の結果を作れるようにする。
構造:
classDiagram
class Director {
-builder: Builder
+construct(): void
}
class Builder {
<<interface>>
+buildPartA(): void
+buildPartB(): void
+getResult(): Product
}
class ConcreteBuilder {
-product: Product
+buildPartA(): void
+buildPartB(): void
+getResult(): Product
}
class Product
Director --> Builder
Builder <|.. ConcreteBuilder
ConcreteBuilder --> Product
TypeScript例:
class Pizza {
private toppings: string[] = [];
private size = "M";
setSize(size: string) { this.size = size; return this; }
addTopping(topping: string) { this.toppings.push(topping); return this; }
describe(): string {
return `サイズ:${this.size} トッピング:${this.toppings.join(", ")}`;
}
}
class PizzaBuilder {
private pizza = new Pizza();
setSize(size: string): this {
this.pizza.setSize(size);
return this;
}
addTopping(topping: string): this {
this.pizza.addTopping(topping);
return this;
}
build(): Pizza {
return this.pizza;
}
}
// メソッドチェーンで段階的にオブジェクトを組み立てる
const pizza = new PizzaBuilder()
.setSize("L")
.addTopping("チーズ")
.addTopping("ベーコン")
.build();
console.log(pizza.describe()); // サイズ:L トッピング:チーズ, ベーコン
試験で問われやすいポイント:
クラスやオブジェクトを組み合わせることで、より大きな構造を実現することが目的。 継承による静的な組み合わせと、委譲(コンポジション)による動的な組み合わせの両方を含む。
目的: あるクラスのインターフェースを、クライアントが期待する別のインターフェースに変換する。 既存クラスをそのまま再利用したい場合に有効。
構造:
classDiagram
class Target {
<<interface>>
+request(): void
}
class Adapter {
-adaptee: Adaptee
+request(): void
}
class Adaptee {
+specificRequest(): void
}
Target <|.. Adapter
Adapter --> Adaptee : delegates
TypeScript例:
// 既存の(変更できない)クラス
class LegacyPrinter {
printOldFormat(text: string): void {
console.log(`[LEGACY] ${text}`);
}
}
// クライアントが期待するインターフェース
interface ModernPrinter {
print(text: string): void;
}
// Adapter: LegacyPrinterをModernPrinterに適合させる
class PrinterAdapter implements ModernPrinter {
constructor(private legacy: LegacyPrinter) {}
print(text: string): void {
this.legacy.printOldFormat(text);
}
}
const printer: ModernPrinter = new PrinterAdapter(new LegacyPrinter());
printer.print("見積書を印刷します"); // [LEGACY] 見積書を印刷します
試験で問われやすいポイント:
目的: オブジェクトに動的に新しい機能を追加する。サブクラス化による機能拡張の代替手段で、 機能の組み合わせを柔軟に行える。
構造:
classDiagram
class Component {
<<interface>>
+operation(): string
}
class ConcreteComponent {
+operation(): string
}
class Decorator {
<<abstract>>
-component: Component
+operation(): string
}
class ConcreteDecoratorA {
+operation(): string
}
class ConcreteDecoratorB {
+operation(): string
}
Component <|.. ConcreteComponent
Component <|.. Decorator
Decorator <|-- ConcreteDecoratorA
Decorator <|-- ConcreteDecoratorB
Decorator --> Component : wraps
TypeScript例:
interface Coffee {
cost(): number;
description(): string;
}
class SimpleCoffee implements Coffee {
cost(): number { return 300; }
description(): string { return "コーヒー"; }
}
abstract class CoffeeDecorator implements Coffee {
constructor(protected coffee: Coffee) {}
cost(): number { return this.coffee.cost(); }
description(): string { return this.coffee.description(); }
}
class MilkDecorator extends CoffeeDecorator {
cost(): number { return super.cost() + 50; }
description(): string { return super.description() + " + ミルク"; }
}
class SyrupDecorator extends CoffeeDecorator {
cost(): number { return super.cost() + 30; }
description(): string { return super.description() + " + シロップ"; }
}
// 機能を入れ子にして動的に組み合わせる
let coffee: Coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SyrupDecorator(coffee);
console.log(`${coffee.description()} : ${coffee.cost()}円`);
// コーヒー + ミルク + シロップ : 380円
試験で問われやすいポイント:
目的: 複数のサブシステム(クラス群)に対する統一された簡易インターフェースを提供し、 利用者がサブシステムの複雑さを意識せずに使えるようにする。
構造:
classDiagram
class Facade {
+operation(): void
}
class SubsystemA {
+operationA(): void
}
class SubsystemB {
+operationB(): void
}
class SubsystemC {
+operationC(): void
}
class Client
Client --> Facade
Facade --> SubsystemA
Facade --> SubsystemB
Facade --> SubsystemC
TypeScript例:
class InventorySystem {
checkStock(item: string): boolean {
console.log(`${item} の在庫を確認`);
return true;
}
}
class PaymentSystem {
charge(amount: number): void {
console.log(`${amount}円を決済`);
}
}
class ShippingSystem {
arrangeShipping(item: string): void {
console.log(`${item} の発送手続き`);
}
}
// Facade: 複雑なサブシステム群をまとめて操作する単一の窓口
class OrderFacade {
private inventory = new InventorySystem();
private payment = new PaymentSystem();
private shipping = new ShippingSystem();
placeOrder(item: string, amount: number): void {
if (this.inventory.checkStock(item)) {
this.payment.charge(amount);
this.shipping.arrangeShipping(item);
console.log("注文完了");
}
}
}
new OrderFacade().placeOrder("ノートPC", 120000);
試験で問われやすいポイント:
目的: 他のオブジェクトへのアクセスを制御する代理オブジェクトを提供する。 生成コストの高い処理の遅延実行、アクセス権限制御、リモートオブジェクトへの中継などに使う。
構造:
classDiagram
class Subject {
<<interface>>
+request(): void
}
class RealSubject {
+request(): void
}
class Proxy {
-realSubject: RealSubject
+request(): void
}
Subject <|.. RealSubject
Subject <|.. Proxy
Proxy --> RealSubject : controls access
TypeScript例:
interface Image {
display(): void;
}
// 生成コストが高い実オブジェクト
class HighResolutionImage implements Image {
constructor(private filename: string) {
console.log(`${filename} を読み込み中...(時間がかかる処理)`);
}
display(): void {
console.log(`${this.filename} を表示`);
}
}
// Proxy: 実際に必要になるまで生成を遅延する(遅延初期化)
class ImageProxy implements Image {
private realImage: HighResolutionImage | null = null;
constructor(private filename: string) {}
display(): void {
if (!this.realImage) {
this.realImage = new HighResolutionImage(this.filename);
}
this.realImage.display();
}
}
const image: Image = new ImageProxy("photo.jpg");
console.log("画像オブジェクト生成完了(まだ読み込まれていない)");
image.display(); // ここで初めて読み込み処理が走る
試験で問われやすいポイント:
目的: 個々のオブジェクト(葉)と、それらを組み合わせた集合体(複合オブジェクト)を 同一のインターフェースで扱えるようにし、再帰的な木構造(階層構造)を表現する。
構造:
classDiagram
class Component {
<<interface>>
+operation(): void
}
class Leaf {
+operation(): void
}
class Composite {
-children: Component[]
+add(c: Component): void
+operation(): void
}
Component <|.. Leaf
Component <|.. Composite
Composite --> Component : children *
TypeScript例:
interface FileSystemNode {
getSize(): number;
print(indent?: string): void;
}
// Leaf: これ以上分解できない要素
class FileNode implements FileSystemNode {
constructor(private name: string, private size: number) {}
getSize(): number { return this.size; }
print(indent = ""): void {
console.log(`${indent}- ${this.name} (${this.size}KB)`);
}
}
// Composite: 子要素を持ち、Leafと同じインターフェースで扱える
class FolderNode implements FileSystemNode {
private children: FileSystemNode[] = [];
constructor(private name: string) {}
add(node: FileSystemNode): void {
this.children.push(node);
}
getSize(): number {
return this.children.reduce((sum, c) => sum + c.getSize(), 0);
}
print(indent = ""): void {
console.log(`${indent}+ ${this.name} (${this.getSize()}KB)`);
this.children.forEach((c) => c.print(indent + " "));
}
}
const root = new FolderNode("プロジェクト");
const src = new FolderNode("src");
src.add(new FileNode("main.ts", 10));
src.add(new FileNode("util.ts", 5));
root.add(src);
root.add(new FileNode("README.md", 2));
root.print();
// + プロジェクト (17KB)
// + src (15KB)
// - main.ts (10KB)
// - util.ts (5KB)
// - README.md (2KB)
試験で問われやすいポイント:
オブジェクト間の責務分担やアルゴリズム・処理フローの組み立て方を整理することが目的。
目的: あるオブジェクト(Subject)の状態が変化したときに、それに依存する 複数のオブジェクト(Observer)に自動的に通知する1対多の依存関係を定義する。
構造:
classDiagram
class Subject {
-observers: Observer[]
+attach(o: Observer): void
+detach(o: Observer): void
+notify(): void
}
class Observer {
<<interface>>
+update(data: any): void
}
class ConcreteSubject {
-state: any
+setState(s: any): void
}
class ConcreteObserverA {
+update(data: any): void
}
class ConcreteObserverB {
+update(data: any): void
}
Subject <|-- ConcreteSubject
Observer <|.. ConcreteObserverA
Observer <|.. ConcreteObserverB
Subject --> Observer : notifies *
TypeScript例:
interface Observer {
update(temperature: number): void;
}
class WeatherStation {
private observers: Observer[] = [];
private temperature = 0;
subscribe(observer: Observer): void {
this.observers.push(observer);
}
setTemperature(temp: number): void {
this.temperature = temp;
this.notifyAll();
}
private notifyAll(): void {
this.observers.forEach((o) => o.update(this.temperature));
}
}
class Display implements Observer {
constructor(private name: string) {}
update(temperature: number): void {
console.log(`${this.name}: 現在の気温は${temperature}度です`);
}
}
const station = new WeatherStation();
station.subscribe(new Display("リビングの表示パネル"));
station.subscribe(new Display("スマホアプリ"));
station.setTemperature(28);
// リビングの表示パネル: 現在の気温は28度です
// スマホアプリ: 現在の気温は28度です
試験で問われやすいポイント:
目的: アルゴリズム(処理方法)をカプセル化して交換可能にし、 利用するクラスの変更なしにアルゴリズムを切り替えられるようにする。
構造:
classDiagram
class Context {
-strategy: Strategy
+setStrategy(s: Strategy): void
+executeStrategy(): any
}
class Strategy {
<<interface>>
+execute(data: any): any
}
class ConcreteStrategyA {
+execute(data: any): any
}
class ConcreteStrategyB {
+execute(data: any): any
}
Context --> Strategy
Strategy <|.. ConcreteStrategyA
Strategy <|.. ConcreteStrategyB
TypeScript例:
interface DiscountStrategy {
calculate(price: number): number;
}
class NoDiscount implements DiscountStrategy {
calculate(price: number): number { return price; }
}
class PercentageDiscount implements DiscountStrategy {
constructor(private percent: number) {}
calculate(price: number): number {
return price * (1 - this.percent / 100);
}
}
class FixedDiscount implements DiscountStrategy {
constructor(private amount: number) {}
calculate(price: number): number {
return Math.max(0, price - this.amount);
}
}
// Context: アルゴリズム(DiscountStrategy)を実行時に切り替える
class ShoppingCart {
constructor(private strategy: DiscountStrategy) {}
setStrategy(strategy: DiscountStrategy): void {
this.strategy = strategy;
}
checkout(price: number): number {
return this.strategy.calculate(price);
}
}
const cart = new ShoppingCart(new NoDiscount());
console.log(cart.checkout(1000)); // 1000
cart.setStrategy(new PercentageDiscount(10));
console.log(cart.checkout(1000)); // 900
試験で問われやすいポイント:
目的: 処理の骨格(アルゴリズムの大枠)を親クラスで定義し、 一部のステップの具体的な実装をサブクラスに委ねる。
構造:
classDiagram
class AbstractClass {
<<abstract>>
+templateMethod(): void
#step1(): void
#step2(): void
}
class ConcreteClassA {
#step1(): void
#step2(): void
}
class ConcreteClassB {
#step1(): void
#step2(): void
}
AbstractClass <|-- ConcreteClassA
AbstractClass <|-- ConcreteClassB
note for AbstractClass "templateMethod()内でstep1, step2を\n決まった順序で呼び出す"
TypeScript例:
abstract class ReportGenerator {
// テンプレートメソッド: 処理の流れを固定する
generate(): void {
this.loadData();
this.formatData();
this.exportReport();
}
protected loadData(): void {
console.log("共通: データを読み込む");
}
protected abstract formatData(): void;
protected exportReport(): void {
console.log("共通: レポートを出力する");
}
}
class CsvReportGenerator extends ReportGenerator {
protected formatData(): void {
console.log("CSV形式に整形する");
}
}
class PdfReportGenerator extends ReportGenerator {
protected formatData(): void {
console.log("PDF形式に整形する");
}
}
new CsvReportGenerator().generate();
// 共通: データを読み込む
// CSV形式に整形する
// 共通: レポートを出力する
試験で問われやすいポイント:
目的: 集合体(コレクション)の内部表現を意識せずに、要素に順番にアクセスする手段を提供する。
構造:
classDiagram
class Aggregate {
<<interface>>
+createIterator(): Iterator
}
class Iterator {
<<interface>>
+hasNext(): boolean
+next(): any
}
class ConcreteAggregate {
+createIterator(): Iterator
}
class ConcreteIterator {
-collection: ConcreteAggregate
-index: number
+hasNext(): boolean
+next(): any
}
Aggregate <|.. ConcreteAggregate
Iterator <|.. ConcreteIterator
ConcreteAggregate ..> ConcreteIterator : creates
TypeScript例:
class TaskCollection {
private tasks: string[] = [];
add(task: string): void {
this.tasks.push(task);
}
// JavaScript/TypeScript標準のIteratorプロトコルを実装
[Symbol.iterator](): Iterator<string> {
let index = 0;
const tasks = this.tasks;
return {
next(): IteratorResult<string> {
if (index < tasks.length) {
return { value: tasks[index++], done: false };
}
return { value: undefined, done: true };
},
};
}
}
const tasks = new TaskCollection();
tasks.add("要件定義");
tasks.add("設計");
tasks.add("実装");
// 内部構造(配列)を知らずに順番にアクセスできる
for (const task of tasks) {
console.log(task);
}
// 要件定義
// 設計
// 実装
試験で問われやすいポイント:
目的: 要求(操作)をオブジェクトとしてカプセル化し、要求の発行者(Invoker)と 実行者(Receiver)を分離する。実行の取り消し(Undo)、再実行、キューイング、ログ記録などに応用できる。
構造:
classDiagram
class Command {
<<interface>>
+execute(): void
+undo(): void
}
class ConcreteCommand {
-receiver: Receiver
+execute(): void
+undo(): void
}
class Receiver {
+action(): void
}
class Invoker {
-command: Command
+setCommand(c: Command): void
+executeCommand(): void
}
Command <|.. ConcreteCommand
ConcreteCommand --> Receiver
Invoker --> Command
TypeScript例:
interface Command {
execute(): void;
undo(): void;
}
// Receiver: 実際の処理を行うオブジェクト
class TextDocument {
private content = "";
append(text: string): void {
this.content += text;
}
removeLast(length: number): void {
this.content = this.content.slice(0, -length);
}
getContent(): string {
return this.content;
}
}
// ConcreteCommand: 操作をオブジェクト化し、Undoも実装
class AppendTextCommand implements Command {
constructor(private doc: TextDocument, private text: string) {}
execute(): void {
this.doc.append(this.text);
}
undo(): void {
this.doc.removeLast(this.text.length);
}
}
// Invoker: コマンドの実行履歴を管理する
class CommandHistory {
private history: Command[] = [];
execute(command: Command): void {
command.execute();
this.history.push(command);
}
undoLast(): void {
const command = this.history.pop();
command?.undo();
}
}
const doc = new TextDocument();
const commandHistory = new CommandHistory();
commandHistory.execute(new AppendTextCommand(doc, "こんにちは"));
commandHistory.execute(new AppendTextCommand(doc, "、世界"));
console.log(doc.getContent()); // こんにちは、世界
commandHistory.undoLast();
console.log(doc.getContent()); // こんにちは
試験で問われやすいポイント:
目的: オブジェクトの状態(State)に応じて振る舞いを変える処理を、 状態ごとのクラスに分割し、状態遷移を明示的に管理する。
構造:
classDiagram
class Context {
-state: State
+setState(s: State): void
+request(): void
}
class State {
<<interface>>
+handle(context: Context): void
}
class ConcreteStateA {
+handle(context: Context): void
}
class ConcreteStateB {
+handle(context: Context): void
}
Context --> State
State <|.. ConcreteStateA
State <|.. ConcreteStateB
ConcreteStateA ..> ConcreteStateB : transitions to
TypeScript例:
interface OrderState {
next(order: Order): void;
description(): string;
}
class Order {
private state: OrderState = new PendingState();
setState(state: OrderState): void {
this.state = state;
}
proceed(): void {
this.state.next(this);
}
getStatus(): string {
return this.state.description();
}
}
class PendingState implements OrderState {
next(order: Order): void { order.setState(new ShippedState()); }
description(): string { return "保留中"; }
}
class ShippedState implements OrderState {
next(order: Order): void { order.setState(new DeliveredState()); }
description(): string { return "発送済み"; }
}
class DeliveredState implements OrderState {
next(order: Order): void { /* これ以上遷移しない */ }
description(): string { return "配達完了"; }
}
const order = new Order();
console.log(order.getStatus()); // 保留中
order.proceed();
console.log(order.getStatus()); // 発送済み
order.proceed();
console.log(order.getStatus()); // 配達完了
試験で問われやすいポイント:
| パターン | カテゴリ | 一言でいうと |
|---|---|---|
| Singleton | 生成 | インスタンスを1つだけに限定する |
| Factory Method | 生成 | 生成するクラスをサブクラスに委ねる |
| Abstract Factory | 生成 | 関連する製品群をまとめて生成する |
| Builder | 生成 | 複雑なオブジェクトを段階的に組み立てる |
| Adapter | 構造 | インターフェースを変換して再利用する |
| Decorator | 構造 | オブジェクトに機能を動的に追加する |
| Facade | 構造 | サブシステムへの統一窓口を提供する |
| Proxy | 構造 | 本物の代わりにアクセスを制御する代理を置く |
| Composite | 構造 | 葉と複合体を同一視して木構造を扱う |
| Observer | 振る舞い | 状態変化を購読者に自動通知する |
| Strategy | 振る舞い | アルゴリズムを交換可能にする |
| Template Method | 振る舞い | 処理の骨格を定義し詳細をサブクラスに委ねる |
| Iterator | 振る舞い | 内部構造を隠して順次アクセスを提供する |
| Command | 振る舞い | 操作をオブジェクト化し発行者と実行者を分離する |
| State | 振る舞い | 状態に応じて振る舞いを切り替える |