Decorator
飾り枠と中身の同一視
概要
いわゆるラッパークラス
本質部分をプロパティに持って、委譲すれば良し
マンガでわかる Decorator
マンガでわかる Decorator #デザインパターン - Qiita
でざぱたんで覚える Decorator
ちびキャラは「デコレータたん」。あらゆる機能スーツを「重ね着」して無限の汎用性を発揮した元・最終兵器の少女で、口癖は「脱いでも凄いんだよー。着るともっと凄いんだよー」。素体はいじらず、機能を一枚ずつ着せ足し・脱がせる——継承ではなく内包+委譲で、実行時に自由に機能を組み替えられるのがDecorator。インターフェースさえ合えば何でも入れ子にでき、Mockテストもしやすい。
出典: いしだけ『でざぱたん: ちびキャラで覚えるデザインパターン』(P.199〜)
登場人物
- Component: 機能追加のコアとしてのインタフェース。デコレーション前のスポンジケーキ
- ConcreteComponent: 具体的なスポンジケーキ
- Decorator: 自分が飾っている対象(Component)をプロパティに持ち、かつComponentを実装している
- ConcreteDecorator: Decoratorを実装
クラス図
このサイトの実装(アイスクリームの例)での対応関係:
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
アイスクリームの例
ディスプレイの例
/**
* アイスクリーム抽象クラス
*/
public abstract class IceCream {
/**
* アイスクリームの情報を返す
*/
public String getInfo(){
return "アイスクリーム";
};
}
/**
* ConcreteComponentなアイスクリーム いちご味
*/
public class StrawBerryIceCream extends IceCream {
@Override
public String getInfo() {
return "イチゴ味" + super.getInfo();
}
}
/**
* Concrete Componentなアイスクリーム バニラ味
*/
public class VanillaIceCream extends IceCream {
@Override
public String getInfo() {
return "バニラ味" + super.getInfo();
}
}
/**
* 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();
}
}
/**
* 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();
}
}
/**
* ConcreteDecoratorなアイス チョコトッピング
*/
public class ChocoTippedIceCream extends IceCream {
private IceCream iceCream;
public ChocoTippedIceCream(IceCream iceCream) {
this.iceCream = iceCream;
}
public String getInfo() {
return "チョコチップ" + this.iceCream.getInfo();
}
}
/**
* ConcreteDecoratorなアイスクリーム ワッフルコーン
*/
public class WaffleCornIceCream extends IceCream {
private IceCream iceCream;
public WaffleCornIceCream(IceCream iceCream) {
this.iceCream = iceCream;
}
public String getInfo() {
return "ワッフルコーン" + this.iceCream.getInfo();
}
}
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());
}
}
public abstract class Border extends Display {
protected Display display; // この飾り枠がくるんでいる「中身」を指す
protected Border(Display display) { // インスタンス生成時に「中身」を引数で指定
this.display = display;
}
}
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));
}
}
}
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;
}
}
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();
}
}
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;
}
}
}
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
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 "アイスクリーム"
}
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()
}
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()
}
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()
}
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()
}
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()
}
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()
}
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
<?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
// 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 "アイスクリーム";
}
}
import { IceCream } from "./ice_cream";
// StrawBerryIceCream: ConcreteComponentなアイスクリーム いちご味
// (Java: `class StrawBerryIceCream extends IceCream`)。
export class StrawBerryIceCream extends IceCream {
getInfo(): string {
return "イチゴ味" + super.getInfo();
}
}
import { IceCream } from "./ice_cream";
// VanillaIceCream: ConcreteComponentなアイスクリーム バニラ味
// (Java: `class VanillaIceCream extends IceCream`)。
export class VanillaIceCream extends IceCream {
getInfo(): string {
return "バニラ味" + super.getInfo();
}
}
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();
}
}
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();
}
}
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();
}
}
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();
}
}
// 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
"""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 "アイスクリーム"
"""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()
"""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()
"""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()
"""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()
"""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()
"""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()
"""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()