Menu
CoddyTech

コンポジットパターン

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

Compositeパターンは、単一のオブジェクトとオブジェクトのグループが同じメソッドを持つツリーを構築するため、コードはグループ全体を1つの項目のように扱えます。バッグには項目を入れることができ、バッグの中にほかのバッグを入れることもできます。また、どのバッグに対しても同じ方法で重さを尋ねられます。

どちらの種類も抽象親クラスを共有します。

# entry.gd
@abstract
class_name Entry
extends RefCounted

var entry_name: String

@abstract func get_weight() -> int
@abstract func display(indent: String) -> void

葉は単一の項目です:

# item.gd
class_name Item
extends Entry

var weight: int

func _init(n: String, w: int) -> void:
	entry_name = n
	weight = w

func get_weight() -> int:
	return weight

func display(indent: String) -> void:
	print(indent, entry_name, " (", weight, ")")

複合オブジェクトはほかのエントリーを保持し、それぞれに問い合わせて答えを求めます。バッグ自体の重さは1です:

# bag.gd
class_name Bag
extends Entry

var contents: Array[Entry] = []

func _init(n: String) -> void:
	entry_name = n

func add(entry: Entry) -> void:
	contents.append(entry)

func get_weight() -> int:
	var total := 1
	for entry in contents:
		total += entry.get_weight()
	return total

func display(indent: String) -> void:
	print(indent, entry_name, " [", get_weight(), "]")
	for entry in contents:
		entry.display(indent + "  ")
# main.gd
var pouch := Bag.new("Pouch")
pouch.add(Item.new("Gem", 2))
var backpack := Bag.new("Backpack")
backpack.add(Item.new("Sword", 8))
backpack.add(pouch)
backpack.display("")
Backpack [12]
  Sword (8)
  Pouch [3]
    Gem (2)

Bag.get_weight()は、エントリがアイテムなのかバッグなのかを確認しません。それぞれに同じメソッドを呼び出し、バッグの場合はその中身について尋ねます。この再帰処理は、どれほど深くてもツリー全体をたどります。

リンクは一方向で、バッグからその中身へ向かいます。各エントリも自分が入っているバッグを保存すると、バッグとそのエントリが互いを指し合い、解放されない参照サイクルができます。エントリが自分の場所を知る必要があるときは、display()がインデントを渡すのと同じように、その情報を引数として渡します。

challenge icon

チャレンジ

中級

Composite パターンを使ってフォルダーツリーを作成します。DiskEntry(disk_entry.gd)は、get_size()、count_files()、list_lines(indent)を宣言します。次のように実装してください。

  • FileEntry:サイズは自身のsizeで、ファイル数は1として数えます。また、list_lines()はインデントに続けてNAME (SIZE KB)を出力します。
  • FolderEntry:add(entry)は子要素を格納します。サイズは子要素のサイズの合計、ファイル数は子要素のファイル数の合計です。また、list_lines()はインデントに続けてNAME/ (SIZE KB)を出力し、その後、インデントにスペースを2つ加えて各子要素を一覧表示します。

用意されたコードは、入力からツリーを構築し(後に続く行数、その後にfolder NAME、file NAME SIZE、または現在のフォルダーを閉じるend)、ツリーを一覧表示して合計を出力します。Rootフォルダーにnotes.txt(4 KB)と、song.mp3(300 KB)を含むMusicフォルダーがある場合、出力は次のとおりです。

Root/ (304 KB)
  notes.txt (4 KB)
  Music/ (300 KB)
    song.mp3 (300 KB)
Total: 304 KB, files: 2

コードはfile_entry.gdとfolder_entry.gdに記述します。main.gdには用意された入出力コードが含まれており、編集できません。

自分で試してみよう

extends Node

func _ready():
	# 提供された入出力コード: そのままにしてください
	var open_folders: Array[FolderEntry] = []
	var root: FolderEntry = null
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		match parts[0]:
			"folder":
				var folder := FolderEntry.new(parts[1])
				if open_folders.is_empty():
					root = folder
				else:
					open_folders.back().add(folder)
				open_folders.append(folder)
			"file":
				open_folders.back().add(FileEntry.new(parts[1], int(parts[2])))
			"end":
				open_folders.pop_back()
	root.list_lines("")
	print("Total: %d KB, files: %d" % [root.get_size(), root.count_files()])
quiz icon腕試し

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

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

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