Menu
CoddyTech

デコレーターパターン

CoddyのGDScriptジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 52/65。

Decoratorパターンは、同じメソッドを持つ別のオブジェクトで対象のオブジェクトをラップすることで、そのオブジェクトに振る舞いを追加します。各ラッパーは内部のオブジェクトを呼び出して独自の処理を加え、ラッパーは任意の順序で重ねることができます。

# weapon.gd
class_name Weapon
extends RefCounted

func get_damage() -> int:
	return 10

func describe() -> String:
	return "Sword"

デコレーターはWeaponを拡張するため、武器が期待される場所ならどこでも使え、ラップする武器を保持します:

# fire_enchant.gd
class_name FireEnchant
extends Weapon

var inner: Weapon

func _init(wrapped: Weapon) -> void:
	inner = wrapped

func get_damage() -> int:
	return inner.get_damage() + 5

func describe() -> String:
	return inner.describe() + " + Fire"

SharpEnchantも同じように記述しますが、get_damage()はinner.get_damage() * 2を返し、describe()は" + Sharp"を追加します。

# main.gd
var weapon: Weapon = Weapon.new()
print(weapon.describe(), ": ", weapon.get_damage())
weapon = FireEnchant.new(weapon)
weapon = SharpEnchant.new(weapon)
print(weapon.describe(), ": ", weapon.get_damage())
Sword: 10
Sword + Fire + Sharp: 30

呼び出しは内側へ進みます。SharpEnchantがFireEnchantに問い合わせ、さらに剣に問い合わせます。剣は10を返し、炎がそれを15にし、鋭さが2倍にして30にします。逆の順序、つまりFireEnchant.new(SharpEnchant.new(Weapon.new()))では、ダメージは10 * 2 + 5となり、25です。

デコレーターがなければ、組み合わせごとに独自のサブクラスが必要になります。FireSword、SharpSword、FireSharpSwordなどです。デコレーターを使えば、2つの小さなクラスですべての組み合わせに対応でき、新しいエンチャントを追加するにはファイルを1つ増やすだけです。

challenge icon

チャレンジ

中級

コーヒーショップでは、デコレーターを使ってドリンクを作ります。Beverage(beverage.gd)はcost()とdescription()を宣言し、Espresso(espresso.gd、$3)とTea(tea.gd、$2)は実装済みです。各デコレーターは、ラップするドリンクをinnerに格納します(コンストラクターは実装済みです)。次のクラスでcost()とdescription()をオーバーライドしてください。

  • Milk:コストを1増やし、説明に + Milkを追加します。
  • Mocha:コストを2増やし、 + Mochaを追加します。
  • Large:中にあるすべてのコストを2倍にし、説明の先頭にLarge を付けます。

提供されたコードは、続く注文の数を読み取った後、1行につき1件の注文を読み取ります。注文はドリンク(espressoまたはtea)と追加要素で構成され、追加要素は左から右の順に適用されます。espresso milk largeとtea mocha milkに対する出力は次のとおりです。

Large Espresso + Milk: $8
Tea + Mocha + Milk: $5

コードはmilk.gd、mocha.gd、large.gdに記述してください。main.gdには提供された入出力コードが含まれており、編集できません。

自分で試してみよう

extends Node

func _ready():
	# 提供された入出力コード:そのままにしてください
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var words := OS.read_string_from_stdin().split(" ")
		var drink: Beverage = Tea.new()
		if words[0] == "espresso":
			drink = Espresso.new()
		for extra in words.slice(1):
			match extra:
				"milk":
					drink = Milk.new(drink)
				"mocha":
					drink = Mocha.new(drink)
				"large":
					drink = Large.new(drink)
		print("%s: $%d" % [drink.description(), drink.cost()])
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: GDScriptオンラインコンパイラ