组合模式可以通过简单的组件构建复杂的树状结构。组合对象是简单的容器对象,容器中的内容则可能是另一个组合对象。
传统上讲,组合对象中的每个组件必须是一个叶节点(即不能包含其他对象)或组合节点。关键之处是组合节点和叶节点可以被同等对待。它的UML结构图是很简单:然而,这个简单的模式却允许我们创建非常复杂的元素搭配并满足组件对象接口的要求。作为一个例子,这里有这样一个复杂的元素搭配:
组合模式在文件/文件夹树中很有用。无论在树中的节点是一个正常文件还是文件夹,他都可以进行诸如移动、赋值或删除节点的操作。我们可以创建一个支持这些操作的组件接口,然后使用组合对象来表示文件夹,用叶节点表示普通文件。当然,在Python中,我们可以又一次的利用鸭子类型来隐式地提供接口,因此我们只需要编写两个类。让我们先来定义这些接口:
class Folder(object):
def __init__(self, name):
self.name = name
self.children = {}
def add_child(self, child):
pass
def move(self, new_path):
pass
def copy(self, new_path):
pass
def delete(self):
pass
class File(object):
def __init__(self, name, contents):
self.name = name
self.contents = contents
def move(self, new_path):
pass
def copy(self, new_path):
pass
def delete(self):
pass
对于每个文件夹(组合)对象,我们可以保留一个子节点的字典。通常,一个列表就足够了,但在这种情况下,字典是按照名字查找子节点的有效方式。我们的路径将被指定为节点名,并通过‘/’字符分隔,这类似与Unix shell中的路径。
思考一下涉及的方法,我们就可以看出,不论是一个文件还是文件夹节点,移动或删除它的方式都是相似的。但是复制文件夹节点时必须要进行递归复制,而复制文件节点则只是一个很小的操作。
为了充分利用相似的操作,我们将一些常用的方法提取到一个父类中,让我们丢弃Component接口,将他变为基类:
class Component(object):
def __init__(self, name):
self.name = name
def move(self, new_path):
new_folder = get_path(new_path)
del self.parent.children[self.name]
new_folder.children[self.name] = self
self.parent = new_folder
def delete(self):
del self.parent.children[self.name]
class Folder(Component):
def __init__(self, name):
super().__init__(name)
self.children = {}
def add_child(self, child):
child.parent = self
self.children[child.name] = child
def copy(self, new_path):
pass
class File(Component):
def __init__(self, name, contents):
super().__init__(name)
self.contents = contents
def copy(self, new_path):
pass
def get_path(path):
names = path.split('/')[1:]
node = root
for name in names:
node = node.children[name]
return node
我们在这里的Component类中创建了move和delete方法。move方法使用一个模块级别的get_path()
函数从预定义的根节点中通过给定的路径找出一个节点。所有的文件都将被添加到该根节点或它的子节点。对于move方法,目标应该是当前存在的文件夹,否则我们将会出现错误。如同这本书中的许多例子一样,没有进行错误处理,这帮助我们将考虑的精力集中于原理。
最后让我们来看看组合文件层次结构是否在正常工作:
root = Folder('')
folder1 = Folder('folder1')
folder2 = Folder('folder2')
root.add_child(folder1)
root.add_child(folder2)
folder11 = Folder('folder11')
folder1.add_child(folder11)
file111 = File('file111', 'contents')
folder11.add_child(file111)
file21 = File('file21', 'ohter contents')
folder2.add_child(file21)
folder2.children
folder2.move('/folder1/folder11')
folder11.children
file21.move('/folder1')
folder1.children
# 输出
{'file21': <__main__.File at 0x22eebaf50b8>}
{'file111': <__main__.File at 0x22eebaf57f0>,
'folder2': <__main__.Folder at 0x22eebaf5a90>}
{'file21': <__main__.File at 0x22eebaf50b8>,
'folder11': <__main__.Folder at 0x22eebaf5be0>}
组合模式对这一的树状结构时极为有用的,包含图形用户界面窗口的小部件层次结构、文件层次结构、树集、图形和HTML DOM。在Python中,按照传统方式来实现组合模式是非常有用的,正如先前的例子演示的那样。有时候,如果正在创建的只是一颗很浅的树,我们可以不使用嵌套列表或嵌套字典,并且不需要向我们先前那样来执行自定义的组件,叶节点和组合类。其他一些时候,我们也可以只实现一个组合类,并把叶节点和组合对象视为同一个类。另外,Python的鸭子类型可以很容易的将其他对象添加到组合层次中,只要他们有正确的接口。
参考:
《Python3 面向对象编程》