设计模式 —— 组合模式

组合模式的定义 将对象组合成树形结构以表示 “ 部分-整体 ” 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。该模式分为以下两种形式
安全形式的组合模式:
设计模式 —— 组合模式
文章图片

透明形式的组合模式:
设计模式 —— 组合模式
文章图片

其中,组合模式所包含的各组成部分的说明如下。

  1. Component:为组合模式中的对象声明接口
  2. Leaf:在组合模式中表示叶结点对象,叶结点对象没有子结点,实现所有在Component的操作(如上图的 operation())。
  3. Composite:表示组合部件(由自身和子节点组合而成),实现操纵子节点的所有方法(如上图的 add(),remove(),getChild()方法);实现所有在Component的操作(如上图的 operation())。
  4. Client:通过Component接口操纵组合部件的对象。
优点:
  • 定义了包含基本对象和组合对象的类层次结构,基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合。
  • 简化了客户代码。客户可以一致地使用组合结构和单个对象,通常用户不知道处理的是一个叶结点还是一个组合组件。
  • 使得更容易增加新类型的组件。新定义的Composite或Leaf子类自动与已有的结构和客户代码一起工作,客户程序不需要因为新的Component类而改变。
  • 使设计变得更通用。
缺点:
  • 在使用安全形式的组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。
使用场景:
  • 您想表示对象的部分-整体层次结构(树形结构)
  • 您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
场景引入 假设我们要创建一个树形菜单,菜单中的树枝和叶子具有相同的 operation() 方法,那么我们要如何设计程序,以便于我们遍历整个树形菜单呢?
方案一:安全形式的组合模式 在 Component 中不去声明管理子类对象的方法(如 add,remove,getChild),那么子类的 Leaf 就不需要实现它,而是在 Composit 声明所有用来管理子类对象的方法。但是安全模式与依赖倒置原则冲突,并且在遍历树形结构的的时候需要进行强制类型转换(如下文客户端代码中的 showTree((Composite)c) )。
设计模式 —— 组合模式
文章图片

步骤一:定义抽象控件
public abstract class Component { //个体和整体都具有 public void operation(){ //编写业务逻辑 } }

步骤二:定义 Leaf 控件
public class Leaf extends Component { /* * 可以覆写父类方法 * public void operation(){ * * } */ }

步骤三:定义 Composite 控件
public class Composite extends Component { //构件容器 private List componentArrayList = new ArrayList(); //增加一个叶子构件或树枝构件 public void add(Component component){ this.componentArrayList.add(component); } //删除一个叶子构件或树枝构件 public void remove(Component component){ this.componentArrayList.remove(component); } //获得分支下的所有叶子构件和树枝构件 public List getChildren(){ return this.componentArrayList; } }

步骤四:定义客户端类
public class Client { public static void main(String[] args) { //创建一个根节点 Composite root = new Composite(); root.operation(); //创建一个树枝构件 Composite branch = new Composite(); //创建一个叶子节点 Leaf leaf = new Leaf(); //建立整体 root.add(branch); branch.add(leaf); } //通过递归遍历树 public static void showTree(Composite root){ for(Component c:root.getChildren()){ if(c instanceof Leaf){ //叶子节点 c.operation(); }else{ //树枝节点 showTree((Composite)c); } } } }

方案二:透明形式的组合模式 在Component中声明所有来管理子对象的方法(如 add,remove,getChild)。那么子类的 Leaf 和 Composit 对于外界没有区别,它们具备完全一致的接口。但是需要将在 Leaf 类中存在的管理子对象的方法屏蔽掉,并且抛出适当的异常。
设计模式 —— 组合模式
文章图片

步骤一:定义抽象控件
public abstract class Component { //个体和整体都具有 public void operation(){ //编写业务逻辑 } //增加一个叶子构件或树枝构件 public abstract void add(Component component); //删除一个叶子构件或树枝构件 public abstract void remove(Component component); //获得分支下的所有叶子构件和树枝构件 public abstract List getChildren(); }

步骤二:定义 Leaf 控件
public class Leaf extends Component { /* * 可以覆写父类方法 * public void operation(){ * * } */ public void add(Component component){ //空实现,抛出“不支持请求”异常 throw new UnsupportedOperationException(); } public void remove(Component component){ //空实现,抛出“不支持请求”异常 throw new UnsupportedOperationException(); } public List getChildren(){ //空实现,抛出“不支持请求”异常 throw new UnsupportedOperationException(); } }

步骤三:定义 Composite 控件
public class Composite extends Component { //构件容器 private List componentArrayList = new ArrayList(); //增加一个叶子构件或树枝构件 public void add(Component component){ this.componentArrayList.add(component); } //删除一个叶子构件或树枝构件 public void remove(Component component){ this.componentArrayList.remove(component); } //获得分支下的所有叶子构件和树枝构件 public List getChildren(){ return this.componentArrayList; } }

步骤四:定义客户端类
public class Client { public static void main(String[] args) { //创建一个根节点 Composite root = new Composite(); root.operation(); //创建一个树枝构件 Composite branch = new Composite(); //创建一个叶子节点 Leaf leaf = new Leaf(); //建立整体 root.add(branch); branch.add(leaf); } //通过递归遍历树 public static void showTree(Component root){ for(Component c:root.getChildren()){ if(c instanceof Leaf){ //叶子节点 c.operation(); }else{ //树枝节点 showTree(c); } } } }

安全模式和透明模式的区别
  1. 安全模式在抽象组件中只定义一些默认的行为或属性,它是把树枝节点和树叶节点彻底分开;透明模式是把用来组合使用的方法放到抽象类中,不管叶子对象还是树枝对象都有相同的结构,因此需要做抛出异常的处理。
  2. 安全模式与依赖倒置原则冲突;透明模式的好处就是它基本遵循了依赖倒置原则,方便系统进行扩展。
  3. 【设计模式 —— 组合模式】安全模式在遍历树形结构的的时候需要进行强制类型转换;在透明模式下,遍历整个树形结构是比较容易的,不用进行强制类型转换。

    推荐阅读