组合模式的定义:将对象组合成树形结构以表示“部分-整体”的层次结构。而客户端在使用时并不需要区分单个对象(叶子对象)和组合对象。
我们来举一个简单的例子,模拟一下windows的文件系统,比如我们要遍历某一个盘符下的所有文件及文件夹,然后以树状结构输出文件及文件夹的名称。
//先定义一个文件夹类 public class Folder { //文件夹的名称 public string Name; public Folder(string name) { Name=name; } //一个文件夹下可能有很多其他文件夹 List<Folder> folders=new List<Folder>(); //也可能有很多文件 List<File> files=new List<File>(); //向该文件夹下添加文件夹 public void AddFolder(Folder folder) { folders.Add(folder); } //移出文件夹下的某个文件夹 public void RemoveFolder(Folder folder) { folders.Remove(folder); } //向该文件夹下添加文件 public void AddFile(File file) { files.Add(file); } //移出该文件夹下某个文件 public void RemoveFile(File file) { files.Remove(file); } //显示该文件夹的名称及其下的文件夹、文件名称 //文件夹名字前面加“+”,文件名字前面加"-" //每向里一层都会在前面加俩空格,用来展示层次结构 //string的一个构造函数new string(char,num)表示输出num个字符 public string ShowName(int depth) { //先显示自己的名字 Console.WriteLine(new string(' ',depth)+“+”+Name); //下一层的名字前要多加俩空格 depth=depth+2; //再显示旗下文件夹的名字 foreach(var folder in folders) { folder.ShowName(depth); } //再显示旗下文件的名字 foreach(var file in files) { file.ShowName(depth); } } } //文件类 public class File { public string Name; public File(string name) { Name=name; } public void string ShowName(int depth) { Console.WriteLine(new string(' ',depth)+“-”+Name); } } //客户端 public class Client { public static void Main(string[] args[]) { Folder folder = new Folder("中外文化学习资料"); Folder folder1_1 = new Folder("中日文化"); Folder folder1_2 = new Folder("港台文化"); File file1_1 = new File("傲慢与偏见.pdf"); File file1_1_1 = new File("苍井空隐退大作.rmvb"); File file1_2_1 = new File("艳照门.rar"); folder.AddFolder(folder1_1); folder.AddFolder(folder1_2); folder.AddFile(file1_1); folder1_1.AddFile(file1_1_1); folder1_1.AddFile(file1_2_1); folder.ShowName(1); } } +中外文化学习资料 +中日文化 -苍井空隐退大作.rmvb +港台文化 -艳照门.rar -傲慢与偏见.pdf
虽然上述代码实现了功能,但可以看出必须区分叶子对象(File)与组合对象(Folder),在Folder与Client里都需要区别对待,区别对待组合对象与叶子对象,不仅让程序变得复杂,还对功能的扩展带来不便。比如现在叶子对象不仅有File,又加了一个其他什么叶子对象,部门不仅需要改Client的代码,还要在Folder里再加一个列表及对应的方法。
所以结合组合模式的定义,这是一个“部分-整体”的树型结构,我们只需要保证对叶子对象与组合对象的操作的一致性就可以了。我们来看下其结构图:
组合模式的关键就在于这个抽象类,它既可以代表叶子对象,也可以代表组合对象,这样客户端在操作的时候,不需要区分叶子对象和组合对象,而且在组合对象里也不需要区分。
//由于要统一两种对象的操作,所以抽象类中的方法也主要是两种对象对外方法的和。 //它里面既有叶子对象的方法,也有组合对象的方法。 //常见做法是对于某些子类没有意义的方法,提供默认实现(或抛出异常) //这样如果子类需要这个方法,那就覆盖实现,不需要就默认父类的实现 public abstract class AbstractClass { //子类的父类实例,也许有些功能会用到 public AbstractClass parent{get;set;} //对所有子类都有的方法,定义成抽象方法 public abstract showName(int depth); //对不是所有子类都有的方法,提供默认实现 public add(AbstractClass a) { throw new UnsupportedOperationException("此对象不支持该方法"); } public remove(AbstractClass a) { throw new UnsupportedOperationException("此对象不支持该方法"); } ... public deleteMyself() { throw new UnsupportedOperationException("此对象不支持该方法"); } } public class Folder:AbstractClass { //文件夹的名称 public string Name; public Folder(string name) { Name=name; } //现在不需要有两个不同的列表了 List<AbstractClass> childs=new List<AbstractClass>(); //也不需要两类Add方法了 public void Add(AbstractClass children) { childs.Add(children); //设置子类的父类实例,也许有些功能会用到 children.parent=this; } //也不需要两类Remove方法了 public void Remove(AbstractClass children) { //这里实现一个功能,就是将某一个节点删除掉的话,将这个节点下的子节点全部移到被删的节点的父节点下面 foreach(var child in children.getChildren()) { child.parent=this; this.childs.add(child); } childs.Remove(children); } public List<AbstractClass> getChildren() { return childs; } //显示该文件夹的名称及其下的文件夹、文件名称 //文件夹名字前面加“+”,文件名字前面加"-" //每向里一层都会在前面加俩空格,用来展示层次结构 //string的一个构造函数new string(char,num)表示输出num个字符 public string ShowName(int depth) { //先显示自己的名字 Console.WriteLine(new string(' ',depth)+“+”+Name); //下一层的名字前要多加俩空格 depth=depth+2; //这里也不需要两个不同的循环了 foreach(var children in childs) { children.ShowName(depth); } } } //文件类 public class File { public string Name; public File(string name) { Name=name; } public void string ShowName(int depth) { Console.WriteLine(new string(' ',depth)+“-”+Name); } public void deleteMyself() { } } //客户端 public class Client { public static void Main(string[] args[]) { //客户端只需要Add就可以了不用区分不同的对象 AbstractClass folder = new Folder("中外文化学习资料"); AbstractClass folder1_1 = new Folder("中日文化"); AbstractClass folder1_2 = new Folder("港台文化"); AbstractClass file1_1 = new File("傲慢与偏见.pdf"); AbstractClass file1_1_1 = new File("苍井空隐退大作.rmvb"); AbstractClass file1_2_1 = new File("艳照门.rar"); folder.Add(folder1_1); folder.Add(folder1_2); folder.Add(file1_1); folder1_1.Add(file1_1_1); folder1_1.Add(file1_2_1); folder.ShowName(1); } } +中外文化学习资料 +中日文化 -苍井空隐退大作.rmvb +港台文化 -艳照门.rar -傲慢与偏见.pdf
我们可以看到在Folder的showName方法里,类似递归,在设计上称作递归关联,与我们通常说的递归算法,递归算法是一个方法会调用方法自己,而这里是一个方法调用子类的同名方法,再在子类里调用子子类的同名方法而已。
组合模式的本质:统一叶子对象和组合对象。