2

在日常生活中存在很多部分和整体的关系,他们都具有一致性,但是有各有个的行为,比如大学中的部门与学院、总公司的部门与分公司、学习用品中的书与书包、生活用品中的衣服与衣柜,以及厨房中的锅碗瓢盆等。

然而在项目开发中也存在相同的道理,当我们做人员管理系统的时候,公司下面是部门,每个部门下面可能会有分组,组之后才是人员,那么依照此例的话,就形成了一个树状的结构,以总公司为根基,一层一层的向下递归。对这些简单的对象来与复合对象的处理,用组合模式模式实现起来可能会更加的恰当和方便。

什么是组合模式

组合模式:组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构。用户对单个对象和组合对象的使用具有一致性。 —— 节选自百度百科

其实在文章开头已经说了比较详尽了,组合模式通过创建对象的组的树形结构,把对象组合成树状结构以表示整体及部分的构建关系。组合模式一句树状结构把对象进行组合,以用来表示部分以及整体之间的层次关系,这种类型的设计模式隶属于结构型设计模式,组合模式使用户对单个对象和组合对象的访问都具备一致性。可以让客户端用同一种方式处理个别对象以及对象组合。当我们的业务属于树状结构的时候,就可以适当的考虑使用组合模式。

组合模式优缺点

组合模式中包含一个容器,然而这个容器是由多个包括容器在内的子容器所构成的,组合模式与其子类都拥有相同的方法。但是,组合模式本身并不完成其中具体的细节的一些工作,而是把这些请求递归的传给自己的子类型,最终完成后获取到最终想要的结果。

优点
  1. 组合模式是得客户端代码可以一致的处理单个对象和组合对象,并不需要关心自己处理的是单个对象还是组合对象,大大的简化了客户端的代码
  2. 组合模式可以更容易的在组合类中加如新的对象,客户端不会因为加入了新的对象而更改源代码,从何满足开闭原则
  3. 使用组合模式时可以利用多态和递归的机制更加方便的使用复杂树构建
缺点
  1. 客户端需要花更多的时间理清类与类之间的层次关系,增大了客户端的复杂程度
  2. 不容易限制容器中的组成部分
  3. 不太能够用继承的方式增加构件的新功能

示例

笔者在翻阅资料的时候,发现组合模式又分成两种,分别是透明模式和安全模式。

组合模式的主要角色如下:

  1. 组件:接口描述了树中简单项目和复杂项目所共有的操作。
  2. 叶节点:是树的基本结构,它不包含子项目。一般情况下,叶节点最终会完成大部分的实际工作,以为他们无法将工作指派给其他部分。
  3. 容器:包含叶节点或其他容器等子项目的代为。容器指导其子项目所属的具体类,它只通过通用的组件接口与其子项目交互。
  4. 客户端:通过组件接口与所有项目交互,因此客户端以相同方式与树状结构中的简单或复杂项目交互。
透明模式

透明模式是把组合使用的方法放到抽象类中,不管叶子对象还是容器都有相同的结构,这样做的好处就是叶子节点对与外界是没有任何区别的,他们具备完全一致的行为接口。但是因为叶对象本身不存在添加和删除功能,所以实现它是没有意义的。

类图如下所示:

image

代码示例:

abstract class Component {

  protected name:string;

  constructor(name:string){
    this.name = name;
  }

  //  增加一个叶子构件或树枝构件
  public abstract add(component:Component):void;

  //  删除一个叶子构件或树枝构件
  public abstract remove(component:Component):void;

  //  获取分支下的所有叶子构件和树枝构件
  public abstract display(depth:number):void;

}

class Composite extends Component {

  private componentArrayList:Component[] = []

  constructor(name:string){
    super(name);
  }

  public add(component:Component): void {
    this.componentArrayList.push(component);
  }

  public remove(component:Component): void {
    const {componentArrayList} = this;
    this.componentArrayList = componentArrayList.filter((el:Component) => component === el);
  }

  public display(depth:number):void {
    //  输出树形结构
    for(let i = 0; i < depth; i++) {
      console.log("------")
    }
    console.log(this.name);
    const {componentArrayList} = this;
    //  遍历下一级
    for (let i = 0,item:Component ;item = componentArrayList[i++];) {
      item.display(depth + 1);
    }
  }
}

class Leaf extends Component {
  
  constructor(name:string){
    super(name);
  }

  public add(component:Component): void {
    throw new Error("Unsupported request");
  }

  public remove(component:Component): void {
    throw new Error("Unsupported request");
  }

  //  输出树形结构的叶子节点
  public display(depth:number): void {
    for(let i = 0; i < depth; i++) {
      console.log('-');
    }
    console.log(this.name);
  }

}

const compositeOne = new Composite("CompositeOne");
const leafOne = new Leaf("CompositeOne-01");
compositeOne.add(leafOne);
compositeOne.add(new Leaf("CompositeOne-02"));
compositeOne.add(new Leaf("CompositeOne-03"));
compositeOne.display(1);
安全模式

安全模式是把树枝节点和树叶节点彻底分开,树枝节点单独又有用来组合的方法,这种方法比较安全。但是由于不够透明,所以树叶节点和树枝节点将具有不同的接口,客户端的调用需要做相应的判断,带来了很大的不便。

类图如下所示:

image

代码示例:

abstract class Component {
  protected name:string;

  constructor(name:string){
    this.name = name;
  }

  public abstract display(depth:number):void;
};

class Composite extends Component {

  private componentArrayList:Component[] = [];

  constructor(name:string){
    super(name);
  }

  public add(component:Component): void {
    this.componentArrayList.push(component);
  }

  public remove(component:Component): void {
    const {componentArrayList} = this;
    this.componentArrayList = componentArrayList.filter((el:Component) => component === el);
  }

  public display(depth:number):void {
    //  输出树形结构
    for(let i = 0; i < depth; i++) {
      console.log("------")
    }
    console.log(this.name);
    const {componentArrayList} = this;
    //  遍历下一级
    for (let i = 0,item:Component ;item = componentArrayList[i++];) {
      item.display(depth + 1);
    }
  }

};

class Leaf extends Component {

  constructor(name:string){
    super(name);
  }

  public display(depth:number):void {
    //输出树形结构的叶子节点
    for(let i=0; i<depth; i++) {
      console.log('-');
    }
    console.log(this.name);
  }

}

const compositeOne = new Composite("CompositeOne");
const leafOne = new Leaf("CompositeOne-01");
compositeOne.add(leafOne);
compositeOne.add(new Leaf("CompositeOne-02"));
compositeOne.add(new Leaf("CompositeOne-03"));
compositeOne.display(1);
小节

通过上述两种方法运行结果均是相同的,却别在于内部实现的而已,一种是叶节点与树枝节点具备一致的行为接口但有空实现的透明模式,另一种是树枝节点单独拥有用来组合的方法但调用不便的安全模式。为什么说它调用不便呢,因为我们如果通过递归遍历树时,这时需要判断当前节点是叶子节点还是树枝节点,客户端就需要相应的判断。

总结

文章中分析了组合模式的结构和特点(组合模式用于组合多个对象形成树形结构以表示具有部分-整体关系地层次结构。包含抽象构件类,叶子构件类,容器构件类),综上所述在业务中需要表示一个对象整体与部分层次结构的场合的时候可以使用组合模式,当对用户隐藏组合对象与单个对象的不同,用户可以用统一的组合结构的对象的时候同样也可以使用组合模式。


Aaron
4k 声望6.1k 粉丝

Easy life, happy elimination of bugs.