Menu
CoddyTech

ストラテジーパターン

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

Strategyパターンでは、アルゴリズムの各バージョンをそれぞれ独自のオブジェクトに格納するため、プログラムの実行中にアルゴリズムを切り替えられます。それを使用するオブジェクトであるコンテキストは、1つのメソッドを呼び出すだけで、どのバージョンを保持しているかを気にしません。

ここでは、2つの攻撃戦略、親クラスと同じメソッド damage() を持つサブクラスを示します。

# attack_style.gd
class_name AttackStyle
extends RefCounted

func damage(power: int) -> int:
	return power
# heavy_attack.gd
class_name HeavyAttack
extends AttackStyle

func damage(power: int) -> int:
	return power * 2

コンテキストは戦略を保持し、計算をその戦略に委ねます:

# fighter.gd
class_name Fighter
extends RefCounted

var power := 10
var style: AttackStyle = AttackStyle.new()

func set_style(new_style: AttackStyle) -> void:
	style = new_style

func attack() -> void:
	print("Hit for ", style.damage(power))
# main.gd
var fighter := Fighter.new()
fighter.attack()
fighter.set_style(HeavyAttack.new())
fighter.attack()
Hit for 10
Hit for 20

Fighterは、自分がどのスタイルを持っているかを確認しません。新しいスタイルは新しいクラスであり、fighter.gdはそのままです。attack()内のif style == "heavy"の連鎖は、スタイルが増えるたびに長くなっていきます。

戦略が独自のデータを持たない小さな関数ひとつであれば、Callableで十分です。コンテキストに関数を保存し、call()で実行します。

# price_calculator.gd
class_name PriceCalculator
extends RefCounted

var discount: Callable = func(price): return price

func final_price(price: int) -> int:
	return discount.call(price)
# main.gd
var calc := PriceCalculator.new()
print(calc.final_price(80))
calc.discount = func(price): return price / 2
print(calc.final_price(80))
calc.discount = func(price): return max(price - 25, 0)
print(calc.final_price(80))
80
40
55

戦略に独自の設定や複数のメソッドがある場合はクラスを使い、短い計算を1つ行う場合はCallableを使います。この考え方は以前にも使いました。sort_custom()に渡す比較関数は、ソート戦略です。

challenge icon

チャレンジ

中級

チェックアウト時に、ショップのカートは割引戦略を切り替えられます。discount.gd の Discount は基底戦略で、割引はありません。さらに2つの戦略とカートを作成してください。

  • PercentOff(パーセントを指定して作成):apply(total) は total - total * percent / 100 を返します(整数演算)。また、10パーセントの場合、label() は 10% off を返します。
  • FixedOff(金額を指定して作成):apply(total) は合計から金額を差し引いた値を返しますが、0 未満にはなりません。また、金額が30の場合、label() は 30 off を返します。
  • Cart:set_discount(new_discount) は戦略を保存し、checkout() は価格を合計して Subtotal TOTAL, LABEL, pay PRICE を返します。ここで PRICE は、戦略の apply() が返す値です。

提供されているコードは、1行目の価格、続くチェックアウトの回数、そして各行に1つずつ戦略を読み取ります。戦略は percent N、fixed N、または none です。価格が 50 30 40 で、戦略が percent 10、fixed 30、none の場合、出力は次のとおりです。

Subtotal 120, 10% off, pay 108
Subtotal 120, 30 off, pay 90
Subtotal 120, no discount, pay 120

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

自分で試してみよう

extends Node

func _ready():
	# 提供された入出力コード:そのままにしてください
	var cart := Cart.new()
	for price in OS.read_string_from_stdin().split(" "):
		cart.add_item(int(price))
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		if parts[0] == "percent":
			cart.set_discount(PercentOff.new(int(parts[1])))
		elif parts[0] == "fixed":
			cart.set_discount(FixedOff.new(int(parts[1])))
		else:
			cart.set_discount(Discount.new())
		print(cart.checkout())
quiz icon腕試し

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

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

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