DP-09: Decorator パターン
継承ではなく「委譲(いじょう)」によってオブジェクトに機能を動的に追加するパターンです。BufferedInputStream や BufferedReader など、 Java 標準ライブラリでも広く使われています。
Decorator パターンとは
あるオブジェクトに機能を追加したいとき、最初に思いつくのは「継承してサブクラスを作る」方法です。 しかし継承では「静的」に機能が決まってしまい、実行時に「この機能だけ追加する」「あの機能は不要」という柔軟な組み合わせができません。 Decorator パターンでは、元のオブジェクトを「ラップ(包む)」する形で機能を追加します。 ラップするオブジェクト(デコレータ)も同じインターフェースを実装しているため、 利用側はデコレータが何重に巻かれていても同じように扱えます。
継承との違い
- 継承(静的): クラス設計時に機能の組み合わせを全て決める必要がある。「Trim かつ UpperCase かつ Logging」のクラスを事前に作らなければならない
- Decorator(動的): 実行時に機能を組み合わせられる。
new LoggingDecorator(new UpperCaseDecorator(new TrimDecorator(...)))のように自由に積み重ねられる
Java 標準ライブラリでは I/O クラスが Decorator パターンの代表例です。 それぞれが同じインターフェース(InputStream / Reader)を実装しながら、 包む度に機能が追加されます。
Java 標準ライブラリの Decorator 実例
// 例1: BufferedReader + InputStreamReader(重ね包み)
// InputStreamReader: バイトストリーム → 文字ストリームへ変換
// BufferedReader: バッファリングを追加して readLine() が使えるようになる
BufferedReader reader = new BufferedReader(
new InputStreamReader(System.in, "UTF-8"));
// 例2: BufferedInputStream(バッファリングのみ追加)
// InputStream をそのままでは1バイトずつ読む
// BufferedInputStream でラップするだけでバッファリングが有効になる
InputStream raw = new FileInputStream("data.bin");
BufferedInputStream buffered = new BufferedInputStream(raw);
// 例3: GZIPOutputStream(圧縮を追加)
// FileOutputStream の上に GZIPOutputStream を重ねるだけで圧縮書き込みになる
OutputStream file = new FileOutputStream("data.gz");
GZIPOutputStream gzip = new GZIPOutputStream(file);サンプルコード
Java 8 では抽象クラス TextProcessorDecorator を使ってデコレータの共通ロジックを集約します。wrapped フィールドに委譲先を保持し、process() を呼び出してから機能を追加します。
よくあるミス・注意点
⚠️ デコレータチェーンが深くなりすぎてデバッグが困難になる
デコレータを何重にも重ねると、どのデコレータが何番目に処理されているか追いにくくなります。 エラーが発生したとき「どのデコレータが原因か」を特定するのが難しくなるため、LoggingDecorator のような ログ出力デコレータを活用して処理の流れを可視化しましょう。 また、デコレータの数が多くなる場合はファクトリメソッドにまとめると管理しやすくなります。
⚠️ デコレータの順序を間違える
デコレータは最も外側から順に処理されます。 例えば new TrimDecorator(new UpperCaseDecorator(...)) とnew UpperCaseDecorator(new TrimDecorator(...)) では 処理の順序が逆になります。「まずトリムしてから大文字化する」か 「まず大文字化してからトリムする」かで結果が変わる場合があります。
⚠️ デコレータを使うべき場面で継承してしまう
「Trim かつ UpperCase かつ Logging の TextProcessor が欲しい」と思ったとき、TrimUpperCaseLoggingTextProcessor という サブクラスを作ってしまうと、機能の組み合わせの数だけクラスが増えてしまいます。 機能の追加・取り外しが実行時に必要な場合は Decorator パターンを検討しましょう。
テストする観点
- 各デコレータが単独で正しく動作すること(
UpperCaseDecoratorは大文字変換、TrimDecoratorは空白除去) - デコレータを2つ以上組み合わせたとき、期待通りの順序で処理されること
- 空文字列(境界値)を渡したとき、各デコレータが正しく動作すること
- null を渡したとき、適切に例外がスローされること(または null 安全に処理されること)
LoggingDecoratorが処理前後に正しくログを出力すること- デコレータの順序を変えたとき(Trim→UpperCase と UpperCase→Trim)、処理結果の違いが期待通りであること