Menu
CoddyTech

组合模式

本课属于 Coddy GDScript 学习旅程的“面向对象编程”单元。第 55 课,共 65 课。

组合模式会构建树,让单个对象和一组对象拥有相同的方法,这样代码就能把整个组当作一个项目来处理。一个袋子可以装物品,也可以装其他袋子,你可以用相同的方式询问它们的重量。

这两种类型有一个共同的抽象父类:

# 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

挑战

中等

使用组合模式构建文件夹树。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),然后使用当前缩进再加两个空格的缩进列出每个子项。

提供的代码会根据输入构建文件夹树(先输入后续行数,再输入 folder NAME、file NAME SIZE 或 end,其中 end 会关闭当前文件夹),然后列出文件夹树并打印总计。对于一个包含 notes.txt(4 KB)和一个包含 song.mp3(300 KB)的 Music 文件夹的 Root 文件夹,输出为:

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 在线编译器