Decorator:一覧表示への期限超過バッジ等の後付け装飾
📋 この記事の目次
TicketCraftの一覧画面には、「期限超過」「緊急」といったバッジを表示する機能があります。今のコードは、TicketRowクラスの中にすべてのバッジ表示ロジックが詰め込まれています。
public class TicketRow {
private String title;
private boolean overdue;
private boolean urgent;
public TicketRow(String title, boolean overdue, boolean urgent) {
this.title = title;
this.overdue = overdue;
this.urgent = urgent;
}
// バッジを追加するたびに、このメソッドの中にif文が増えていく
public String render() {
String result = title;
if (overdue) {
result = "⏰[期限超過] " + result;
}
if (urgent) {
result = "🔥[緊急] " + result;
}
return result;
}
}
新しいバッジ(例えば「担当者未割り当て」)を追加するたびに、booleanフィールドとif文のペアが増えていきます。
Decoratorは何のためにあるか
Decoratorは、「既存のクラスを変更せずに、機能を後から重ね着させる」 パターンです。無地のTシャツにワッペンを縫い付けたり、上着を重ね着したりするように、元のオブジェクトを包む形で機能を追加していきます。
Before:装飾のたびに元のクラスにif文が増える
public class Main {
public static void main(String[] args) {
TicketRow row = new TicketRow("ログイン画面が真っ白になる", true, true);
System.out.println(row.render());
}
}
実行結果:
🔥[緊急] ⏰[期限超過] ログイン画面が真っ白になる
このやり方だと、TicketRowクラス自身が「どんなバッジがあるか」を全部知っていなければならず、バッジの種類が増えるほどクラスが肥大化していきます。
After:バッジをDecoratorとして重ねる
まず、共通のインターフェースを定義します。
public interface Renderable {
String render();
}
public class BasicTicketRow implements Renderable {
private String title;
public BasicTicketRow(String title) {
this.title = title;
}
@Override
public String render() {
return title;
}
}
各バッジを、Renderableを包み込むDecoratorとして実装します。
public abstract class TicketRowDecorator implements Renderable {
protected final Renderable wrapped;
protected TicketRowDecorator(Renderable wrapped) {
this.wrapped = wrapped;
}
}
public class OverdueBadgeDecorator extends TicketRowDecorator {
public OverdueBadgeDecorator(Renderable wrapped) {
super(wrapped);
}
@Override
public String render() {
return "⏰[期限超過] " + wrapped.render();
}
}
public class UrgentBadgeDecorator extends TicketRowDecorator {
public UrgentBadgeDecorator(Renderable wrapped) {
super(wrapped);
}
@Override
public String render() {
return "🔥[緊急] " + wrapped.render();
}
}
public class Main {
public static void main(String[] args) {
Renderable row = new UrgentBadgeDecorator(
new OverdueBadgeDecorator(
new BasicTicketRow("ログイン画面が真っ白になる")));
System.out.println(row.render());
}
}
実行結果:
🔥[緊急] ⏰[期限超過] ログイン画面が真っ白になる
結果はBeforeと同じですが、BasicTicketRow自身はバッジについて何も知りません。新しいバッジ(「担当者未割り当て」)を追加したくなったら、TicketRowDecoratorを継承した新しいクラスを1つ足すだけで済み、BasicTicketRowは一切変更する必要がありません。バッジを重ねる順番も、呼び出し側で自由に組み替えられます。
🧓 ベテランの現場コラム Decoratorは、Java標準ライブラリの入出力クラス(ストリームをラップして機能を追加していく仕組み)でも実際に使われている、実務での登場頻度が高いパターンです。「オプション機能を後から柔軟に組み合わせたい」と感じたら候補に挙がりますが、包む層が何重にもなると、今度は「結局どのバッジがどんな順番で適用されているか」が読みにくくなることもあります。重ねる数が多くなりすぎないか、要所要所で見直しましょう。
こんな場面で使う/使わない
- 使う: 基本機能に対して、複数のオプション機能を自由な組み合わせで後付けしたい場合
- 使わない: 追加したい機能が1〜2種類で固定しており、組み合わせを増やす予定がない場合。素直にif文で書いた方が読みやすいこともある
まとめ
- Decoratorは「既存のクラスを変更せず、機能を後から重ね着させる」パターン
- 新しい装飾を追加しても、既存のクラスやDecoratorには手を入れずに済む
- 重ねすぎると読みにくくなるため、組み合わせの数と可読性のバランスを見る
🎓 理解度テスト
学んだ内容を確認しましょう。全3問。