组合模式(Composite)
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
使用场景
1、用于对象的部分-整体层次结构,如树形菜单、文件夹菜单、部门组织架构图等;
2、对用户隐藏组合对象与单个对象的不同,使得用户统一地使用组合结构中的所有对象。
说到树,“数据结构”这门课上都学习过了,树的遍历问题最重要了。下面按照类图直接用简单的代码实现一番。
// 抽象构件类、节点类 abstract class Component { public String name; public Component(String name) { this.name = name; } // 公有操作 public void getName() { System.out.println(this.name); } }
// 树枝构件类 class Composite extends Component { private LinkedList<Component> children; public Composite(String name) { super(name); this.children = new LinkedList<Component>(); } // 添加一个节点,可能是树枝、叶子 public void add(Component child) { this.children.add(child); } // 删除一个节点,可能是树枝、叶子 public void remove(String child) { this.children.remove(child); } // 获取子树 public LinkedList<Component> getChildren() { return this.children; } }
// 树叶构件类 class Leaf extends Component { public Leaf(String name) { super(name); } }
// 测试类,负责构建整棵树 public class Client { public static void main(String[] args) { Composite root = new Composite("树根"); Composite branch01 = new Composite("树枝01"); Composite branch02 = new Composite("树枝02"); root.add(branch01); root.add(branch02); Component leaf01 = new Leaf("树叶01"); Component leaf02 = new Leaf("树叶02"); Component leaf03 = new Leaf("树叶03"); Component leaf04 = new Leaf("树叶04"); Component leaf05 = new Leaf("树叶05"); branch01.add(leaf01); branch01.add(leaf02); branch02.add(leaf03); branch02.add(leaf04); branch02.add(leaf05); displayTree(root); } // 递归遍历整棵树 public static void displayTree(Composite root) { LinkedList<Component> children = root.getChildren(); for (Component c : children) { if (c instanceof Leaf) { System.out.print("\t"); c.getName(); } else { c.getName(); // 递归 displayTree((Composite)c); } } } }
测试结果:
树枝01 树叶01 树叶02 树枝02 树叶03 树叶04 树叶05
上面的 Client 类构建树的代码让人看了觉得烦,如果整棵树下来有几百个节点,这样子的工作效率太糟糕了。其实,在实际应用中,并不是这样子手工地构建一棵复杂的树的,应该是我们已经将整棵树的节点内容、逻辑关系都存储在数据库表中(更重要的是这录入工作应该不是我们开发人员做的),由于表中的各个节点记录都保存有自身的一些相关信息,包括是否为树叶、父节点等等,开发人员需要的就是让程序从数据库中的表中读取记录来构建整棵树。
此外,上面的代码中只能从根节点往下遍历,不能够从某一节点开始往上遍历,解决这个问题可以在抽象构件类 Component 类中添加一个 parent 属性,再添加相应 setParent() 、 getParent()方法即可。而关于不同的遍历方法再具体实现一下就完成了。
上面的类图是属于安全模式的,因为 Leaf 类不具有 add 、 remove 等方法,这些具体方法是被下置到 Composite 类(树枝节点类)中去具体实现了。
如果要实现透明模式,类图如下:
差别仅在于将 add 、remove 等方法上升到抽象构件类 Component 中去了。那么此时 Leaf 类在具体实现时就必须将继承而来的 add 、remove 等不可用、不合逻辑的方法给注解 Deprecated 掉,并抛出适当的异常,不提供给用户使用。看起来这种透明模式似乎更加麻烦,没事找事。其实,这种模式下使得我们在遍历整棵树的时候可以不进行强制类型转换。看看上面的 displayTree() 方法,里面在使用递归遍历时就使用到了 (Composite)c 强制类型转换了。
Leaf 类代码如下:
// 树叶构件类 class Leaf extends Component { public Leaf(String name) { super(name); } @Deprecated // 抛出不支持的操作异常 public void add(Component child) throws UnsupportedOperationException{ throws new UnsupportedOperationException(); } @Deprecated public void remove(String child) throws UnsupportedOperationException{ throws new UnsupportedOperationException(); } @Deprecated public LinkedList<Component> getChildren() throws UnsupportedOperationException{ throws new UnsupportedOperationException(); } }