习惯从一个例子入手。我边写边做这个例子。
假如说现在有这样子的需求,让你模仿系统的个人文件夹做一个应用。说白了让你做个资源管理的东东。给个例子如下图:
做一个应用起头应该想一下怎么把这个需求抽象出一个一个的模型。看下这个发现有点像树这种数据结构,那的确是树。
怎么表示一棵树?
你写过没?我写过N 多次了,学习数据结构的时候经常写到二叉树,但是这次不是二叉,所以还是有点不同,我是这么写。
首先是有结点类,表示每一个结点。
public class Node { private String name; private ArrayList<Node> sons; public Node(String name) {// 构造方法让sons初始化 this.name = name; sons = new ArrayList<Node>(); } public void display() {// 打印结点的名字 System.out.println(name); } public void addSon(Node son) {// 添加一个结点 sons.add(son); } public void traverse() {// 遍历该节点下面的文件夹和文件 for (Node son : sons) { System.out.println(son.getName()); if (son.getSons().size() != 0) {// 如果不是叶子结点,还得继续追下去 son.traverse(); } } } //各种setters()和getters()。 <!-- [if gte mso 9]><xml>
接下来是树类。
public class Tree { private Node root; public Tree(Node root) { this.root = root; } public void traverse() { root.traverse(); } }
接下来是场景。如下:
public class Client { public static void main(String[] args) { Node root = new Node("个人文件夹"); Tree userProfile = new Tree(root); // 下面是组装的过程 // 定义第一级子目录,然后给根节点 Node document = new Node("文档"); Node collection = new Node("收藏"); Node music = new Node("音乐"); Node readme = new Node("readme.txt"); root.addSon(music); root.addSon(document); root.addSon(collection); root.addSon(readme); // 给“文档”添加子目录和文件 Node log = new Node("日志"); Node data = new Node("资料"); Node work = new Node("工作"); document.addSon(log); document.addSon(data); document.addSon(work); // 给“收藏”添加子目录和文件 collection.addSon(new Node("火狐收藏")); collection.addSon(new Node("http://jason61719.iteye.com")); // 给“日志”添加文件 log.addSon(new Node("04.17.txt")); log.addSon(new Node("04.18.txt")); log.addSon(new Node("04.19.txt")); // 现在让他们打印出来 userProfile.traverse(); } }
结果:
音乐 文档 日志 04.17.txt 04.18.txt 04.19.txt 资料 工作 收藏 火狐收藏 http://jason61719.iteye.com readme.txt
一切正常。如果说这个就是组合模式你信不信?我不信。难道我随手一写就是个模式?事实上,这个真的不是。不过组合模式中有一种叫“透明的组合模式”跟这个就有点类似。
说回来,其实我们用Node 这个类去表示folder (文件夹)和file (文件)这两种类型,但实际上只是因为我们的业务很简单,如果系统还要求file 必须记录扩展名,用户,修改时间,大小,编码格式等等,这些都是folder 没有的,这时候还用一个类去表示这2 个完全不同的模型,那就太不应该了,模型设计完以后还得考虑数据存储,如果用一个类去表示这2 个模型,那么数据库的冗余字段就太多了!所以应该设计多个类。
要遵循面向接口编程的原则,为了方便给上层调用,统一一个接口。实际上在这里接口并不好用,有些方法是一样的,属性是一样的,如果用抽象类那是更好的选择。如下代码:
//抽象类,抽象出文件和文件夹 public abstract class AbstractFile { protected String name; public AbstractFile(String name) { this.name = name; } public void display() { System.out.println(name); } public abstract void traverse(); } // 具体的实现类文件夹 public class Folder extends AbstractFile { private ArrayList<AbstractFile> sons; public Folder(String name) { super(name); sons = new ArrayList<AbstractFile>(); } public void addSon(AbstractFile son) { sons.add(son); } @Override public void traverse() { for (AbstractFile son : sons) { if (son instanceof File) { System.out.println("fileName\t\t" + son.name); } else { System.out.println("folderName\t" + son.name); son.traverse(); } } } } //具体的实现类文件 public class File extends AbstractFile { private String expandName;// 扩展名 private Integer size;// 大小 public File(String name) { super(name); } @Override public void traverse() { System.out.println("fileName\t\t" + name); } //各种getters和setters } //场景 public static void main(String[] args) { Folder root = new Folder("个人文件夹"); // 下面是组装的过程 // 定义第一级子目录,然后给根节点 Folder document = new Folder("文档"); Folder collection = new Folder("收藏"); Folder music = new Folder("音乐"); File readme = new File("readme.txt"); root.addSon(music); root.addSon(document); root.addSon(collection); root.addSon(readme); // 给“文档”添加子目录和文件 Folder log = new Folder("日志"); Folder data = new Folder("资料"); Folder work = new Folder("工作"); document.addSon(log); document.addSon(data); document.addSon(work); // 给“收藏”添加子目录和文件 collection.addSon(new File("火狐收藏")); collection.addSon(new File("http://jason61719.iteye.com")); // 给“日志”添加文件 log.addSon(new File("04.17.txt")); log.addSon(new File("04.18.txt")); log.addSon(new File("04.19.txt")); // 现在让他们打印出来 root.traverse(); }
好吧接下来该解说一下了。
组合模式跟普通的处理类之间关系的模式不大一样,事实上是用来描述一种特殊的类组合的关系。UML 里面的组合关系就是指一种比聚合依赖和关联都强的关系,是一种has-a 的关系。这几个关系表现在代码是一样的,不过在数据库的存储就不一样。算是有限制条件的存储。不说这个,我主要是想说明组合关系是has-a 关系。组合关系不仅是has-a ,而且是contains-a ,就是说是描述部分和整体关系的。在代码中经常的体现就是部分类以一个List 或者数组的形式出现在整体类中,如上例子中Folder 中有ArrayList<AbstractFile> 。
如果我们用一个类来表示这整个资源管理的模型,显然是不靠谱。首先这个资源是有深度的,而且深度不确定,第二是这个资源类别有很多种,还有很多原因。
这时候我们应该用组合模式来描述这种组合关系。为什么呢?先说说组合模式的定义和它的几个特点。
定义:将对象以树形结构组织起来, 以达成“ 部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性.
这在上面就表现为客户端不必关心自己处理的是单个对象还是组合结构,他们看见的都是一个接口或者抽象类。
优点:高层模块的调用变得简单;节点的增加变得自由
缺点:在封装的过程要声明这是叶节点还是分支,所以这是不符合面向接口编程的。
参考:《设计模式之禅》