はじめに
デコレーターパターンを調べていると、抽象クラスが登場し必要以上に複雑化していると感じました。
ここではデコレーターパターンの本質を最小限のサンプルコードを用いて解説したいと思います。
デコレーターパターンとは
- デコレーター...装飾者
- 装飾対象オブジェクトを飾り枠で装飾することによって機能の拡張ができる
- 飾り枠と装飾対象を同一視する(同じインターフェースまたは抽象クラスを実装・継承する)ことによって、クライアントは違いを意識することなく使える
- デコレーターの実装クラスは、装飾対象のインスタンスをフィールドにもつ
メリット
- 既存のオブジェクトに変更を加えず、機能を動的に追加できる
- デコレーターを組み合わせて多様な機能を実現できる
- 責務を分離できる
デメリット
- デコレーターが多重の場合、処理の順序を理解するにコストがかかる
- デコレーターによっては順序に依存するものがあり、適切な順序を考慮する必要がある
シンプルな例を試してみる
- お題:プレーンテキストを様々なHTMLタグで装飾してみる
- 登場人物
- Text.ts...装飾される対象のインターフェース
- PlainText.ts...Textインターフェースの実装。装飾対象オブジェクト
- BoldDecorator.ts...bタグ(太字)で装飾する。装飾対象をフィールドにもつ
- ItalicDecorator.ts...iタグ(斜体)で装飾する。装飾対象をフィールドにもつ
クラス図
main.ts
import { Text } from "./Text";
import { PlainText } from "./PlainText";
import { BoldDecorator } from "./Decorators/BoldDecorator";
import { ItalicDecorator } from './Decorators/ItalicDecorator'
let text: Text = new PlainText("Hello, World!");
console.log("デコレーターなし: " + text.format());
text = new BoldDecorator(text);
console.log('Boldのみ: ' + text.format());
text = new ItalicDecorator(text);
console.log('Italic + Bold: ' + text.format());
Text.ts
export interface Text {
format(): string;
}
PlainText.ts
import {Text} from "./Text";
export class PlainText implements Text {
private text: string;
constructor (text: string) {
this.text = text;
}
public format(): string{
return this.text;
}
}
Decorators/BoldDecorator.ts
import { Text } from "../Text";
export class BoldDecorator implements Text {
private component: Text;
constructor(component: Text){
this.component = component;
}
public format(): string {
return `<b>${this.component.format()}</b>`
}
}
Decorators/ItalicDecorator.ts
import { Text } from "../Text";
export class ItalicDecorator implements Text {
private component: Text;
constructor(component: Text){
this.component = component;
}
public format(): string{
return `<i>${this.component.format()}</i>`
}
}
実行結果:
おわりに
もはや雑なメモになってしまいましたが、個人的に以下のポイントを押さえておくと理解しやすいと感じました。
- デコレーターの実装クラスは装飾対象をフィールドにもつ
- デコレーターと装飾対象は同じインターフェースまたは抽象クラスを実装・継承する

