← 一覧に戻る
構造に関するパターン

Decorator

飾り枠と中身の同一視

概要

いわゆるラッパークラス

本質部分をプロパティに持って、委譲すれば良し

マンガでわかる Decorator

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

でざぱたんで覚える Decorator

ちびキャラは「デコレータたん」。あらゆる機能スーツを「重ね着」して無限の汎用性を発揮した元・最終兵器の少女で、口癖は「脱いでも凄いんだよー。着るともっと凄いんだよー」。素体はいじらず、機能を一枚ずつ着せ足し・脱がせる——継承ではなく内包+委譲で、実行時に自由に機能を組み替えられるのがDecorator。インターフェースさえ合えば何でも入れ子にでき、Mockテストもしやすい。

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

登場人物

  • Component: 機能追加のコアとしてのインタフェース。デコレーション前のスポンジケーキ
  • ConcreteComponent: 具体的なスポンジケーキ
  • Decorator: 自分が飾っている対象(Component)をプロパティに持ち、かつComponentを実装している
  • ConcreteDecorator: Decoratorを実装

クラス図

480px-Decorator_UML_class_diagram.svg.png (480×380)

このサイトの実装(アイスクリームの例)での対応関係:

classDiagram
  class IceCream {
    <<abstract>>
    +getInfo() String
  }
  class VanillaIceCream {
    +getInfo() String
  }
  class StrawBerryIceCream {
    +getInfo() String
  }
  class DoubleIceCream {
    -iceCream IceCream
    +getInfo() String
  }
  class TripleIceCream {
    -iceCream IceCream
    +getInfo() String
  }
  class ChocoTippedIceCream {
    -iceCream IceCream
    +getInfo() String
  }
  class WaffleCornIceCream {
    -iceCream IceCream
    +getInfo() String
  }
  IceCream <|-- VanillaIceCream
  IceCream <|-- StrawBerryIceCream
  IceCream <|-- DoubleIceCream
  IceCream <|-- TripleIceCream
  IceCream <|-- ChocoTippedIceCream
  IceCream <|-- WaffleCornIceCream
  DoubleIceCream o-- IceCream
  TripleIceCream o-- IceCream
  ChocoTippedIceCream o-- IceCream
  WaffleCornIceCream o-- IceCream

やり方

  • DecoratorはComponentを実装し、さらにComponentをプロパティとしてもつ
  • こうすることでConcreteDecoratorはConcreteComponentの機能を全て使うことができるようになる

メリット

  • 中身を変えずに機能追加ができる
  • DecoratorもComponentも同一インタフェースを持つが、包めば包むほど機能が追加される
  • その際包まれるComponentの修正は必要なし
  • 単純な品揃えでも多様な機能追加ができる
  • アイスクリームのトッピングを、バニラ、チョコ、ストロベリー、キウイ、組み合わせとして

関連

Adapter

目的が違う

  • Adapterは「右と左の端子の形式が違うから、それらを接続するインタフェースが必要」という意図
  • Decoratorは「左右の端子があっているところに、それと同じ形の端子を付けた装置をを割り込ませる」
  • 「右も左もUSBだけど、平文じゃなく暗号化できるコードをかましますよ」

Javaソースに見る

Java.ioパッケージクラスはDecoratorパターンを採用している

これにより極めて多様な機能追加が実現できる

//普通にファイルを読み込む
Reader reader = new FileReader(filename);
//バッファリングして読み込む
Reader reader = new BufferReader(new FileReader(filename));
//行番号を指定し、バッファリングして読み込む
Reader reader = new LineNumberReader(new BufferReader(new FileReader(filename)));
//行番号は管理するけど、バッファリングしない
Reader reader = new LineNumberReader(new FileReader(filename));
//行番号を管理しバッファリングするが、ファイルからでなくネットワークから読み込む
java.net.Socket socket = new Socket(hostname,portNumber);
Reader reader = new LineNumberReader(new BufferReader(new InputStreamReader(socket.getInputStream())));
Java

アイスクリームの例

ディスプレイの例

アイスクリーム例
IceCream.java
/**
 * アイスクリーム抽象クラス
 */
public abstract class IceCream {
  /**
   * アイスクリームの情報を返す
   */
  public  String getInfo(){
    return "アイスクリーム";
  };
}
StrawBerryIceCream.java
/**
 * ConcreteComponentなアイスクリーム いちご味
 */
public class StrawBerryIceCream extends IceCream {

  @Override
  public String getInfo() {
    return "イチゴ味" + super.getInfo();
  }

}
VanillaIceCream.java
/**
 * Concrete Componentなアイスクリーム バニラ味
 */

public class VanillaIceCream extends IceCream {

  @Override
  public String getInfo() {
    return "バニラ味" + super.getInfo();
  }
}
DoubleIceCream.java
/**
 * ConcreteDecoratorなアイスクリーム 2段重ね(全部同じ味)
 */
public class DoubleIceCream extends IceCream {
  private IceCream iceCream;

  public DoubleIceCream(IceCream iceCream) {
    this.iceCream = iceCream;
  }

  @Override
  public String getInfo() {
    return "2段重ね" + this.iceCream.getInfo();
  }
}
TripleIceCream.java
/**
 * ConcreteDecoratorなアイスクリーム 3段重ね(全部同じ味)
 */
public class TripleIceCream extends IceCream {
  private IceCream iceCream;

  public TripleIceCream(IceCream iceCream) {
    this.iceCream = iceCream;
  }

  @Override
  public String getInfo() {
    return "3段重ね" + this.iceCream.getInfo();
  }
}
ChocoTippedIceCream.java
/**
 * ConcreteDecoratorなアイス チョコトッピング
 */
public class ChocoTippedIceCream extends IceCream {
  private IceCream iceCream;

  public ChocoTippedIceCream(IceCream iceCream) {
    this.iceCream = iceCream;
  }

  public String getInfo() {
    return "チョコチップ" + this.iceCream.getInfo();
  }

}
WaffleCornIceCream.java
/**
 * ConcreteDecoratorなアイスクリーム ワッフルコーン
 */
public class WaffleCornIceCream extends IceCream {
  private IceCream iceCream;

  public WaffleCornIceCream(IceCream iceCream) {
    this.iceCream = iceCream;
  }

  public String getInfo() {
    return "ワッフルコーン" + this.iceCream.getInfo();
  }
}
Main.java
public class Main {
  public static void main(String[] args) {
    // 重ねないバニラアイス
    IceCream ic1 = new VanillaIceCream();
    System.out.println(ic1.getInfo());
    // 3段重ねバニラ
    IceCream ic2 = new TripleIceCream(new VanillaIceCream());
    System.out.println(ic2.getInfo());
    // ワッフルコーンイチゴ味
    IceCream ic3 = new WaffleCornIceCream(new StrawBerryIceCream());
    System.out.println(ic3.getInfo());
    // 2段重ねワッフルコーンバニラ
    IceCream ic4 = new DoubleIceCream(new WaffleCornIceCream(new VanillaIceCream()));
    System.out.println(ic4.getInfo());
    // チョコチップバニラ
    IceCream ic5 = new ChocoTippedIceCream(new VanillaIceCream());
    System.out.println(ic5.getInfo());
    // めちゃくちゃだけど以下のようなこともできる
    IceCream chaos = new TripleIceCream(
        new DoubleIceCream(new WaffleCornIceCream(new TripleIceCream(new VanillaIceCream()))));
    System.out.println(chaos.getInfo());
  }
}
教科書公式版 (罫線Border/Display例)
Border.java
public abstract class Border extends Display {
    protected Display display;          // この飾り枠がくるんでいる「中身」を指す
    protected Border(Display display) { // インスタンス生成時に「中身」を引数で指定
        this.display = display;
    }
}
Display.java
public abstract class Display {
    public abstract int getColumns();               // 横の文字数を得る
    public abstract int getRows();                  // 縦の行数を得る
    public abstract String getRowText(int row);     // row番目の文字列を得る
    public void show() {                            // 全部表示する
        for (int i = 0; i < getRows(); i++) {
            System.out.println(getRowText(i));
        }
    }
}
SideBorder.java
public class SideBorder extends Border {
    private char borderChar;                        // 飾りとなる文字
    public SideBorder(Display display, char ch) {   // コンストラクタでDisplayと飾り文字を指定
        super(display);
        this.borderChar = ch;
    }
    public int getColumns() {                       // 文字数は中身の両側に飾り文字分を加えたもの
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {                          // 行数は中身の行数に同じ
        return display.getRows();
    }
    public String getRowText(int row) {             // 指定行の内容は、中身の指定行の両側に飾り文字をつけたもの
        return borderChar + display.getRowText(row) + borderChar;
    }
}
FullBorder.java
public class FullBorder extends Border {
    public FullBorder(Display display) {
        super(display);
    }
    public int getColumns() {                   // 文字数は中身の両側に左右の飾り文字分を加えたもの
        return 1 + display.getColumns() + 1;
    }
    public int getRows() {                      // 行数は中身の行数に上下の飾り文字分を加えたもの
        return 1 + display.getRows() + 1;
    }
    public String getRowText(int row) {         // 指定した行の内容
        if (row == 0) {                                                 // 上端の枠
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else if (row == display.getRows() + 1) {                      // 下端の枠
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else {                                                        // それ以外
            return "|" + display.getRowText(row - 1) + "|";
        }
    }
    private String makeLine(char ch, int count) {         // 文字chをcount個連続させた文字列を作る
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}
StringDisplay.java
public class StringDisplay extends Display {
    private String string;                          // 表示文字列
    public StringDisplay(String string) {           // 引数で表示文字列を指定
        this.string = string;
    }
    public int getColumns() {                       // 文字数
        return string.getBytes().length;
    }
    public int getRows() {                          // 行数は1
        return 1;
    }
    public String getRowText(int row) {             // rowが0のときのみ返す
        if (row == 0) {
            return string;
        } else {
            return null;
        }
    }
}
Main.java
public class Main {
    public static void main(String[] args) {
        Display b1 = new StringDisplay("Hello, world.");
        Display b2 = new SideBorder(b1, '#');
        Display b3 = new FullBorder(b2);
        b1.show();
        b2.show();
        b3.show();
        Display b4 = 
                    new SideBorder(
                        new FullBorder(
                            new FullBorder(
                                new SideBorder(
                                    new FullBorder(
                                        new StringDisplay("こんにちは。")
                                    ),
                                    '*'
                                )
                            )
                        ),
                        '/'
                    );
        b4.show();
    }
}
Go

Java版のabstract class IceCreamは、デフォルト実装(getInfo())を持つ実装継承。Goには実装継承が無いため、IceCream(interface)とbaseIceCream(デフォルト実装を持つstruct)に役割を分割し、ConcreteComponentだけがbaseIceCreamを埋め込む(ConcreteDecoratorは埋め込まず、IceCreamをフィールドとして持つ委譲)。

実行: go run ./GoF/patterns/Decorator/go

$ go run ./GoF/patterns/Decorator/go
ice_cream.go
package main

// IceCream is the Component role.
//
// Java版はここを `abstract class IceCream` にしており、抽象クラス自身が
// 具体的なデフォルト実装(getInfo() -> "アイスクリーム")を持つ
// (Strategy/Builderパターンの Java interface とは違い、実装ありの継承)。
// Go にはクラス継承が無いので、この一つの「抽象クラス」を素直には移植できない。
// ここでは役割を二つに分けて表現する:
//
//  1. IceCream (interface) ... Component / ConcreteDecorator が満たすべき型の契約
//     (Java版のIceCreamが「型」として振る舞う部分)。
//  2. baseIceCream (struct) ... デフォルト実装の置き場所
//     (Java版のIceCream.getInfo()が「実装」を持つ部分)。
//     ConcreteComponent(StrawBerry/Vanilla)はこれをembeddingし、
//     `s.baseIceCream.GetInfo()` と明示的に呼ぶことで
//     Javaの`super.getInfo()`を再現する(strawberry_ice_cream.go参照)。
//
// 一方でConcreteDecorator(Double/Triple/ChocoTipped/WaffleCorn)はbaseIceCreamを
// embeddingしない。Decoratorの本質は「包んでいる相手(IceCream)に処理を委譲する」
// ことであり、それはembedding(is-a)ではなくフィールドとして持つ(has-a)ほうが
// 実体に合う(double_ice_cream.go参照)。Go embeddingでsuper.getInfo()もどきを
// やるのはComponent側の便宜であって、Decorator側にまで広げると
// 「継承のエミュレートそのもの」が目的化してしまう。
type IceCream interface {
	GetInfo() string
}

// baseIceCream is where Java's `IceCream.getInfo()` default body lives.
// ConcreteComponent structs embed this to borrow it, exactly the way a Java
// subclass borrows its superclass's method body via `super`.
type baseIceCream struct{}

func (baseIceCream) GetInfo() string {
	return "アイスクリーム"
}
strawberry_ice_cream.go
package main

// StrawBerryIceCream is a ConcreteComponent
// (Java: `class StrawBerryIceCream extends IceCream`)。
// baseIceCreamをembeddingし、`s.baseIceCream.GetInfo()`でJavaの
// `super.getInfo()`を再現する(ice_cream.goのコメント参照)。
type StrawBerryIceCream struct {
	baseIceCream
}

func (s StrawBerryIceCream) GetInfo() string {
	return "イチゴ味" + s.baseIceCream.GetInfo()
}
vanilla_ice_cream.go
package main

// VanillaIceCream is a ConcreteComponent
// (Java: `class VanillaIceCream extends IceCream`)。
// baseIceCreamをembeddingし、`s.baseIceCream.GetInfo()`でJavaの
// `super.getInfo()`を再現する(ice_cream.goのコメント参照)。
type VanillaIceCream struct {
	baseIceCream
}

func (s VanillaIceCream) GetInfo() string {
	return "バニラ味" + s.baseIceCream.GetInfo()
}
double_ice_cream.go
package main

// DoubleIceCream is a ConcreteDecorator
// (Java: `class DoubleIceCream extends IceCream`、フィールドに包んでいる
// IceCreamを保持)。2段重ね(全部同じ味を二重化するイメージ)。
// baseIceCreamをembeddingしない理由はice_cream.goのコメント参照
// (Decoratorは委譲(has-a)であって継承(is-a)の代用ではない)。
type DoubleIceCream struct {
	iceCream IceCream
}

func NewDoubleIceCream(iceCream IceCream) *DoubleIceCream {
	return &DoubleIceCream{iceCream: iceCream}
}

func (d *DoubleIceCream) GetInfo() string {
	return "2段重ね" + d.iceCream.GetInfo()
}
triple_ice_cream.go
package main

// TripleIceCream is a ConcreteDecorator
// (Java: `class TripleIceCream extends IceCream`、フィールドに包んでいる
// IceCreamを保持)。3段重ね(全部同じ味を三重化するイメージ)。
// baseIceCreamをembeddingしない理由はice_cream.goのコメント参照。
type TripleIceCream struct {
	iceCream IceCream
}

func NewTripleIceCream(iceCream IceCream) *TripleIceCream {
	return &TripleIceCream{iceCream: iceCream}
}

func (t *TripleIceCream) GetInfo() string {
	return "3段重ね" + t.iceCream.GetInfo()
}
choco_tipped_ice_cream.go
package main

// ChocoTippedIceCream is a ConcreteDecorator
// (Java: `class ChocoTippedIceCream extends IceCream`、フィールドに包んでいる
// IceCreamを保持)。チョコチップトッピング。
// baseIceCreamをembeddingしない理由はice_cream.goのコメント参照。
type ChocoTippedIceCream struct {
	iceCream IceCream
}

func NewChocoTippedIceCream(iceCream IceCream) *ChocoTippedIceCream {
	return &ChocoTippedIceCream{iceCream: iceCream}
}

func (c *ChocoTippedIceCream) GetInfo() string {
	return "チョコチップ" + c.iceCream.GetInfo()
}
waffle_corn_ice_cream.go
package main

// WaffleCornIceCream is a ConcreteDecorator
// (Java: `class WaffleCornIceCream extends IceCream`、フィールドに包んでいる
// IceCreamを保持)。ワッフルコーントッピング。
// baseIceCreamをembeddingしない理由はice_cream.goのコメント参照。
type WaffleCornIceCream struct {
	iceCream IceCream
}

func NewWaffleCornIceCream(iceCream IceCream) *WaffleCornIceCream {
	return &WaffleCornIceCream{iceCream: iceCream}
}

func (w *WaffleCornIceCream) GetInfo() string {
	return "ワッフルコーン" + w.iceCream.GetInfo()
}
main.go
package main

import "fmt"

// 実行:
//
//	go run ./GoF/patterns/Decorator/go
//
// Java版Main.javaと同じ6パターンをそのまま出力する。
func main() {
	// 重ねないバニラアイス
	ic1 := VanillaIceCream{}
	fmt.Println(ic1.GetInfo())

	// 3段重ねバニラ
	ic2 := NewTripleIceCream(VanillaIceCream{})
	fmt.Println(ic2.GetInfo())

	// ワッフルコーンイチゴ味
	ic3 := NewWaffleCornIceCream(StrawBerryIceCream{})
	fmt.Println(ic3.GetInfo())

	// 2段重ねワッフルコーンバニラ
	ic4 := NewDoubleIceCream(NewWaffleCornIceCream(VanillaIceCream{}))
	fmt.Println(ic4.GetInfo())

	// チョコチップバニラ
	ic5 := NewChocoTippedIceCream(VanillaIceCream{})
	fmt.Println(ic5.GetInfo())

	// めちゃくちゃだけど以下のようなこともできる
	chaos := NewTripleIceCream(
		NewDoubleIceCream(NewWaffleCornIceCream(NewTripleIceCream(VanillaIceCream{}))),
	)
	fmt.Println(chaos.GetInfo())
}
PHP
index.php
<?php

/**
 * オブジェクトの重ね着で機能の組み合わせを無限大に
 * 「委譲による継承」
 */


/**
 * 単に継承するだけではなく
 * 継承元クラスのインスタンスを内包して
 * 単にsuperを呼ぶのではなく
 * 組み合わせを自由に変えることができる
 */


/**
 * 拡張対象クラス
 */


class Inner
{
  public function __construct()
  {
  }
  public function getName()
  {
    return __CLASS__;
  }
}

/**
 * 一番内側のクラスを拡張
 */


class Outer extends Inner
{
  private $inner = null;

  public function __construct(Inner $inner)
  {
    $this->inner = $inner;
  }

  public function getName()
  {
    $name = $this->inner->getName();
    return $name . " in Outer";
  }
}

/**
 * さらに拡張
 */


class OuterOfOuter extends Outer
{
  private $inner;

  public function __construct($inner)
  {
    $this->inner = $inner;
  }


  public function getName()
  {
    $name = $this->inner->getName();
    return $name . " in OuterOfOuter";
  }
}


/**
 * さらにさらに拡張
 */


class SuperOuterOfOuter extends OuterOfOuter
{
  private $inner;

  public function __construct($inner)
  {
    $this->inner = $inner;
  }

  public function getName()
  {
    $name = $this->inner->getName();
    return $name . " in SuperOuterOfOuter";
  }
}


$obj = new OuterOfOuter(new Outer(new Inner()));
print $obj->getName();
print "<br>";

$obj = new SuperOuterOfOuter(new Outer(new Inner()));
print $obj->getName();
print "<br>";

$obj = new Outer(new SuperOuterOfOuter(new Outer(new SuperOuterOfOuter(new Inner()))));
print $obj->getName();
print "<br>";
TypeScript

abstract class + super()呼び出しがそのまま使えるので、Java版とほぼ1:1で移植できる。

実行: npx tsx GoF/patterns/Decorator/typescript/main.ts

$ npx tsx GoF/patterns/Decorator/typescript/main.ts
ice_cream.ts
// IceCream: Java版の `abstract class IceCream` をそのまま踏襲する。
//
// Java版はここが抽象クラスで、しかも具体的なデフォルト実装
// (getInfo() -> "アイスクリーム") を持っている
// (Strategy/Builderパターンで使ったJava interfaceとは違い、実装ありの継承)。
// TypeScriptのabstract classはJavaと同じく「実装のあるメソッド」を書けるので、
// Go版のようにinterface+embeddingで擬似的に継承を作る必要はない
// (Go版ice_cream.goのコメント参照)。サブクラスは`super.getInfo()`で
// このデフォルト実装をそのまま呼べる。
export abstract class IceCream {
  getInfo(): string {
    return "アイスクリーム";
  }
}
strawberry_ice_cream.ts
import { IceCream } from "./ice_cream";

// StrawBerryIceCream: ConcreteComponentなアイスクリーム いちご味
// (Java: `class StrawBerryIceCream extends IceCream`)。
export class StrawBerryIceCream extends IceCream {
  getInfo(): string {
    return "イチゴ味" + super.getInfo();
  }
}
vanilla_ice_cream.ts
import { IceCream } from "./ice_cream";

// VanillaIceCream: ConcreteComponentなアイスクリーム バニラ味
// (Java: `class VanillaIceCream extends IceCream`)。
export class VanillaIceCream extends IceCream {
  getInfo(): string {
    return "バニラ味" + super.getInfo();
  }
}
double_ice_cream.ts
import { IceCream } from "./ice_cream";

// DoubleIceCream: ConcreteDecoratorなアイスクリーム 2段重ね(全部同じ味)
// (Java: `class DoubleIceCream extends IceCream`、フィールドに包んでいる
// IceCreamを保持)。
export class DoubleIceCream extends IceCream {
  constructor(private readonly iceCream: IceCream) {
    super();
  }

  getInfo(): string {
    return "2段重ね" + this.iceCream.getInfo();
  }
}
triple_ice_cream.ts
import { IceCream } from "./ice_cream";

// TripleIceCream: ConcreteDecoratorなアイスクリーム 3段重ね(全部同じ味)
// (Java: `class TripleIceCream extends IceCream`、フィールドに包んでいる
// IceCreamを保持)。
export class TripleIceCream extends IceCream {
  constructor(private readonly iceCream: IceCream) {
    super();
  }

  getInfo(): string {
    return "3段重ね" + this.iceCream.getInfo();
  }
}
choco_tipped_ice_cream.ts
import { IceCream } from "./ice_cream";

// ChocoTippedIceCream: ConcreteDecoratorなアイス チョコトッピング
// (Java: `class ChocoTippedIceCream extends IceCream`、フィールドに包んでいる
// IceCreamを保持)。
export class ChocoTippedIceCream extends IceCream {
  constructor(private readonly iceCream: IceCream) {
    super();
  }

  getInfo(): string {
    return "チョコチップ" + this.iceCream.getInfo();
  }
}
waffle_corn_ice_cream.ts
import { IceCream } from "./ice_cream";

// WaffleCornIceCream: ConcreteDecoratorなアイスクリーム ワッフルコーン
// (Java: `class WaffleCornIceCream extends IceCream`、フィールドに包んでいる
// IceCreamを保持)。
export class WaffleCornIceCream extends IceCream {
  constructor(private readonly iceCream: IceCream) {
    super();
  }

  getInfo(): string {
    return "ワッフルコーン" + this.iceCream.getInfo();
  }
}
main.ts
// Decorator パターン: アイスクリーム (Java版と同じ題材)
//
// 実行:
//   npx tsx main.ts
//
// Java版Main.javaと同じ6パターンをそのまま出力する (Go版main.goと同じ構成)。

import { VanillaIceCream } from "./vanilla_ice_cream";
import { StrawBerryIceCream } from "./strawberry_ice_cream";
import { DoubleIceCream } from "./double_ice_cream";
import { TripleIceCream } from "./triple_ice_cream";
import { ChocoTippedIceCream } from "./choco_tipped_ice_cream";
import { WaffleCornIceCream } from "./waffle_corn_ice_cream";

function main(): void {
  // 重ねないバニラアイス
  const ic1 = new VanillaIceCream();
  console.log(ic1.getInfo());

  // 3段重ねバニラ
  const ic2 = new TripleIceCream(new VanillaIceCream());
  console.log(ic2.getInfo());

  // ワッフルコーンイチゴ味
  const ic3 = new WaffleCornIceCream(new StrawBerryIceCream());
  console.log(ic3.getInfo());

  // 2段重ねワッフルコーンバニラ
  const ic4 = new DoubleIceCream(new WaffleCornIceCream(new VanillaIceCream()));
  console.log(ic4.getInfo());

  // チョコチップバニラ
  const ic5 = new ChocoTippedIceCream(new VanillaIceCream());
  console.log(ic5.getInfo());

  // めちゃくちゃだけど以下のようなこともできる
  const chaos = new TripleIceCream(
    new DoubleIceCream(new WaffleCornIceCream(new TripleIceCream(new VanillaIceCream()))),
  );
  console.log(chaos.getInfo());
}

main();
Python

TypeScript同様、abstract base class + super()でJava版とほぼ1:1移植。

実行: python3 GoF/patterns/Decorator/python/main.py

$ python3 GoF/patterns/Decorator/python/main.py
ice_cream.py
"""IceCream: Java版の `abstract class IceCream` に対応する。

Java版はここが抽象クラスで、しかも具体的なデフォルト実装
(get_info() -> "アイスクリーム") を持つ(=抽象メソッドを持たない抽象クラス)。
Strategy/Builder版のPython実装はJavaのinterfaceに合わせて`ABC`+
`@abstractmethod`を使ったが、今回のIceCreamには抽象メソッドが無いため、
素朴な基底クラスとして書けば足りる。サブクラスは`super().get_info()`で
このデフォルト実装をそのまま呼べる(Go版のようなembeddingでの代用は不要。
Go版ice_cream.goのコメント参照)。
"""

from __future__ import annotations


class IceCream:
    def get_info(self) -> str:
        return "アイスクリーム"
strawberry_ice_cream.py
"""StrawBerryIceCream: ConcreteComponentなアイスクリーム いちご味
(Java: `class StrawBerryIceCream extends IceCream`)。
"""

from __future__ import annotations

from ice_cream import IceCream


class StrawBerryIceCream(IceCream):
    def get_info(self) -> str:
        return "イチゴ味" + super().get_info()
vanilla_ice_cream.py
"""VanillaIceCream: ConcreteComponentなアイスクリーム バニラ味
(Java: `class VanillaIceCream extends IceCream`)。
"""

from __future__ import annotations

from ice_cream import IceCream


class VanillaIceCream(IceCream):
    def get_info(self) -> str:
        return "バニラ味" + super().get_info()
double_ice_cream.py
"""DoubleIceCream: ConcreteDecoratorなアイスクリーム 2段重ね(全部同じ味)
(Java: `class DoubleIceCream extends IceCream`、フィールドに包んでいる
IceCreamを保持)。
"""

from __future__ import annotations

from ice_cream import IceCream


class DoubleIceCream(IceCream):
    def __init__(self, ice_cream: IceCream) -> None:
        self._ice_cream = ice_cream

    def get_info(self) -> str:
        return "2段重ね" + self._ice_cream.get_info()
triple_ice_cream.py
"""TripleIceCream: ConcreteDecoratorなアイスクリーム 3段重ね(全部同じ味)
(Java: `class TripleIceCream extends IceCream`、フィールドに包んでいる
IceCreamを保持)。
"""

from __future__ import annotations

from ice_cream import IceCream


class TripleIceCream(IceCream):
    def __init__(self, ice_cream: IceCream) -> None:
        self._ice_cream = ice_cream

    def get_info(self) -> str:
        return "3段重ね" + self._ice_cream.get_info()
choco_tipped_ice_cream.py
"""ChocoTippedIceCream: ConcreteDecoratorなアイス チョコトッピング
(Java: `class ChocoTippedIceCream extends IceCream`、フィールドに包んでいる
IceCreamを保持)。
"""

from __future__ import annotations

from ice_cream import IceCream


class ChocoTippedIceCream(IceCream):
    def __init__(self, ice_cream: IceCream) -> None:
        self._ice_cream = ice_cream

    def get_info(self) -> str:
        return "チョコチップ" + self._ice_cream.get_info()
waffle_corn_ice_cream.py
"""WaffleCornIceCream: ConcreteDecoratorなアイスクリーム ワッフルコーン
(Java: `class WaffleCornIceCream extends IceCream`、フィールドに包んでいる
IceCreamを保持)。
"""

from __future__ import annotations

from ice_cream import IceCream


class WaffleCornIceCream(IceCream):
    def __init__(self, ice_cream: IceCream) -> None:
        self._ice_cream = ice_cream

    def get_info(self) -> str:
        return "ワッフルコーン" + self._ice_cream.get_info()
main.py
"""Decorator パターン: アイスクリーム (Java版と同じ題材)

実行:
    python3 main.py

Java版Main.javaと同じ6パターンをそのまま出力する (Go/TS版main.*と同じ構成)。
"""

from __future__ import annotations

from choco_tipped_ice_cream import ChocoTippedIceCream
from double_ice_cream import DoubleIceCream
from strawberry_ice_cream import StrawBerryIceCream
from triple_ice_cream import TripleIceCream
from vanilla_ice_cream import VanillaIceCream
from waffle_corn_ice_cream import WaffleCornIceCream


def main() -> None:
    # 重ねないバニラアイス
    ic1 = VanillaIceCream()
    print(ic1.get_info())

    # 3段重ねバニラ
    ic2 = TripleIceCream(VanillaIceCream())
    print(ic2.get_info())

    # ワッフルコーンイチゴ味
    ic3 = WaffleCornIceCream(StrawBerryIceCream())
    print(ic3.get_info())

    # 2段重ねワッフルコーンバニラ
    ic4 = DoubleIceCream(WaffleCornIceCream(VanillaIceCream()))
    print(ic4.get_info())

    # チョコチップバニラ
    ic5 = ChocoTippedIceCream(VanillaIceCream())
    print(ic5.get_info())

    # めちゃくちゃだけど以下のようなこともできる
    chaos = TripleIceCream(
        DoubleIceCream(WaffleCornIceCream(TripleIceCream(VanillaIceCream())))
    )
    print(chaos.get_info())


if __name__ == "__main__":
    main()