设计模式-组合模式-场景应用解析

872 阅读6分钟

组合模式的定义&特点

组合(Composite Pattern)模式的定义:整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式,举个简单的例子,如下图所示:

image.png 电脑桌面为根节点,回收站、我的电脑、游戏文件夹为树叶节点,文件1、文件2、ppt文件、工作文件夹、王者荣耀为树叶节点。

先简单通过颜色进行区分,其实根节点和树枝节点本质上属于同一种数据类型,都为棕红色,都可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。组合模式中,会把树枝节点和叶子节点实现同一个接口,方便统一控制。


优点:

  1. 定义层次的关系上,又忽略了层次之间的差异,方便客户端对整个层次结构的控制。
  2. 新增、删除节点的时候,客户端无需修改代码,满足“开闭原则”;


缺点:

  1. 层级关系梳理复杂。
  2. 不容易限制类型,举个例子:游戏文件夹只能包含游戏类的文件,使用组合模式时,不能依赖类型系统进行约束,它们都来自于节点的抽象层,在这种情况下,必须通过在运行时进行类型检查,这样就变得比较复杂。
  3. 设计变得更加的抽象化。

组合模式的结构与实现

1. 组合模式的结构都有哪些角色组成?

答:组合模式包含以下3个主要角色。

(1)抽象构件(Component)角色 Interface:它作为树叶和树枝 共同实现的接口,定义共同的方法。

  • 在透明式组合模式中 定义 了访问和管理树叶子类的方法;
  • 在安全式的组合模式中 则 不定义 访问和管理树叶子类的方法,管理工作由树枝构件完成。 (2)树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

(3)树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件定义的方法。

2、组合模式有哪两种类型呢?

答:2种

(1) 透明方式

  • 抽象构件接口声明了所有子类中的全部方法,客户端无须区别树叶对象和树枝对象。
  • 树叶构件本来没有 Add()、Remove() 及 GetChild()方法,因为实现了抽象构建接口,被迫要实现它们,一般情况是抛出异常,或者空方法,这样会带来一些安全性问题。

为了更清晰的理解,我们举个栗子~上代码!

1、抽象构建类


/**
* 抽象构件类
 **/
public abstract class Component {
  public abstract void add(Component c);
  public abstract void remove(Component c);
  public abstract Component getChild(int i);
  public abstract ArrayList<Component> getChilds();
  public abstract void touch();

  public String name;

  public Component(String name) {
    this.name = name;
  }
  // 公有操作
  public void getName() {
    System.out.println(this.name);
  }
}

2、树枝构件类


/**
 *  树枝构件类
 **/
public class Composite extends Component {
  public Composite(String name) {
    super(name);
    this.children = new ArrayList<>();
  }

  private ArrayList<Component> children = new ArrayList<>();

  public void add(Component c) {
    children.add(c);
  }

  public void remove(Component c) {
    children.remove(c);
  }

  public Component getChild(int i) {
    return children.get(i);
  }

  public ArrayList<Component> getChilds() {
    return children;
  }

  public void touch() {
    for (Object obj : children) {
      ((Component) obj).touch();
    }
  }
}

3、叶子构建类


/**
 * 叶子构件
 **/
public class Leaf extends Component {
  Leaf(String name) {
    super(name);
  }

  public void add(Component c) {
    throw new UnsupportedOperationException("操作不支持");
  }

  public void remove(Component c) {
    throw new UnsupportedOperationException("操作不支持");
  }

  public Component getChild(int i) {
    throw new UnsupportedOperationException("操作不支持");
  }

  @Override
  public ArrayList<Component> getChilds() {
    throw new UnsupportedOperationException("操作不支持");
  }

  public void touch() {
    System.out.println("执行:" + name + " 业务逻辑!");
  }
}

3、测试


/**
 * 测试
 **/
public class CompositePattern {
  public static void main(String[] args) {
    Composite root = new Composite("树根节点");
    Composite c0 = new Composite("树枝节点A");
    Composite c1 = new Composite("树枝节点B");
    Component leaf1 = new Leaf("树叶1");
    Component leaf2 = new Leaf("树叶2");
    Component leaf3 = new Leaf("树叶3");
    Component leaf4 = new Leaf("树叶4");
    root.add(c0);
    root.add(c1);
    c0.add(leaf1);

    c1.add(leaf2);
    c1.add(leaf3);
    c1.add(leaf4);
    root.touch();

    recurrenceTree(root);
  }

  private static void recurrenceTree(Composite root) {
    ArrayList<Component> children = root.getChilds();
    for (Component c : children) {
      if (c instanceof Leaf) {
        System.out.print("\t");
        c.getName();
      } else {
        c.getName();
        // 递归
        recurrenceTree((Composite) c);
      }
    }
  }
}

执行结果如下:

image.png

(2) 安全方式

  • 只有树枝构件中实现了管理子构件的方法,抽象构件不再声明对子对象的管理方法、树叶构件不再实现管理子类的方方,避免了透明方式中的安全性的问题。
  • 测试类在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。

为了便于理解,我们在透明模式的基础上升级为安全模式,大家一比对就清楚了。

1、抽象构建类-去掉了管理子类的方法


/**
* 抽象构件类、节点类
 **/
public abstract class Component {

  public abstract void touch();

  public String name;

  public Component(String name) {
    this.name = name;
  }
  // 公有操作
  public void getName() {
    System.out.println(this.name);
  }
}

2、叶子构建类-不再实现管理子类的方法。


/**
 * 叶子构件
 **/
public class Leaf extends Component {
  Leaf(String name) {
    super(name);
  }

  public void touch() {
    System.out.println("执行:" + name + " 业务逻辑!");
  }
}

说了半天,组合模式的实际应用场景有没有例子呢??

答:有 比如:计算公司预算情况,需要按照每个部门来进行分类、汇总,一个大公司下面有产品部门、研发部门等,产品部门可能分为一级、二级、三级等。

和之前的demo不太一样的地方,这里并没有定义为abstract,而是interface接口(原理一样~)上代码~ 1、抽象构建类


/**
 * 抽象构建
 **/
public interface Jiesuan {
  //计算公司预算
   float calculMoney();
   //获取部门人员具体预算
   void getMsg();
}

2、树枝构建类


/**
 * 树枝构建
 **/
public class JiesuanComposite implements Jiesuan {
  private String name;
  private ArrayList<Jiesuan> orderJiesuan = new ArrayList<>();

  public JiesuanComposite() {
  }

  public JiesuanComposite(String name) {
    this.name = name;
  }

  public void add(Jiesuan c) {
    orderJiesuan.add(c);
  }

  public void remove(Jiesuan c) {
    orderJiesuan.remove(c);
  }

  public Jiesuan getChild(int i) {
    return orderJiesuan.get(i);
  }

  public float calculMoney() {
    float s = 0;
    for (Object obj : orderJiesuan) {
      s += ((Jiesuan) obj).calculMoney();
    }
    return s;
  }

  @Override
  public void getMsg() {
    System.out.println(name+"预算");
    for (Object obj : orderJiesuan) {
      ((Jiesuan) obj).getMsg();
    }
  }
}

3、叶子构建类


/**
 *叶子构建
 **/
public class Yusuan implements Jiesuan {
  private String name; //名字
  private Float peoplePrice; //预算

  //默认构造器
  public Yusuan() {
  }

  //自定义构造器
  public Yusuan(String name, Float peoplePrice) {
    this.name = name;
    this.peoplePrice = peoplePrice;
  }

  @Override
  public float calculMoney() {
    return peoplePrice;
  }

  @Override
  public void getMsg() {
   System.out.println(name+":"+peoplePrice);
  }
}

4、测试


/**
 * 测试
 **/
public class JiesuanPattern {
  public static void main(String[] args) {
    Float s ;
    Yusuan yusuan;
    JiesuanComposite gongsi = new JiesuanComposite("公司");
    JiesuanComposite cpLevel1 =new JiesuanComposite("产品一级部门");
    JiesuanComposite yfLevel1 = new JiesuanComposite("研发一级部门");
    JiesuanComposite yfLevel2A = new JiesuanComposite("研发二级部门A");
    JiesuanComposite yfLevel2B = new JiesuanComposite("研发二级部门B");
    yusuan = new Yusuan("产品一级部门-孙三", 1117f);
    cpLevel1.add(yusuan);
    yusuan = new Yusuan("研发一级部门-孙四",  2222f);
    yfLevel1.add(yusuan);
    yusuan = new Yusuan("研发二级部门A-李莉", 1211f);
    yfLevel2A.add(yusuan);
    yusuan = new Yusuan("研发二级部门A-李磊", 1211f);
    yfLevel2A.add(yusuan);
    yusuan = new Yusuan("研发二级部门B-陈好",  2231f);
    yfLevel2B.add(yusuan);

    yfLevel1.add(yfLevel2A);
    yfLevel1.add(yfLevel2B);

    gongsi.add(cpLevel1);
    gongsi.add(yfLevel1);
    gongsi.getMsg();
    s = gongsi.calculMoney();
    System.out.println("公司总预算:" + s + "元");
  }

}

结果打印如下:

image.png