【Design Pattern】Strctural - Composite

Posted by 西维蜀黍 on 2018-11-12, Last Modified on 2022-12-10

动机

  • 希望表达具有层次结构(部分-整体,part-whole)的实体
  • 对调用者来说,叶子元素和复合元素都是一样的

定义

组合模式(Composite pattern)通常用于表达具有层次结构(part-whole)的类。

注意,这里的层次结构不一定是树状结构,也可能是简单一个集合。

构成

  • 抽象构件角色(Component):是这个具有层次结构的类中的基本单元,可以是接口或抽象类。这个角色给出共有接口及其默认行为。
  • 树叶构件角色(Leaf):位于层级结构中底部的元素,实现了基本元素(Base Component)
  • 树枝构件角色(Composite):由叶子元素组成,实现了基本元素(Base Component)

对调用者来说,叶子元素和复合元素都是一样的,或者说,调用者可以将树叶构件角色和树枝构件角色都看做是基本元素(Component)

当形成树状结构时,他们的关系可以呈以下结构:

UML图

讨论

组合模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。组合模式可以不提供父对象的管理方法,但组合模式必须在合适的地方提供子对象的管理方法(诸如:add()、remove()、getChild()等)。

透明方式

作为第一种选择,在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及getChild()方法。 这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与合成类对象的区别起码在接口层次上消失了,客户端可以同等同的对待所有的对象。这 就是透明形式的组合模式。

这个选择的缺点是不够安全,因为树叶类对象和合成类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild()方法没有意义,是在编译时期不会出错,而只会在运行时期才会出错。

安全方式

第二种选择是在Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。

这个选择的缺点是不够透明,因为树叶类和合成类将具有不同的接口。

这两个形式各有优缺点,需要根据软件的具体情况做出取舍决定。

例子1

UML图

实现思路

基本元素(Base Component)- Shape.java

public interface Shape {
    public void draw(String fillColor);
}

叶子元素(Leaf)- Triangle.java

public class Triangle implements Shape {

    @Override
    public void draw(String fillColor) {
        System.out.println("Drawing Triangle with color "+fillColor);
    }
}

叶子元素(Leaf)- Circle.java

public class Circle implements Shape {

    @Override
    public void draw(String fillColor) {
        System.out.println("Drawing Circle with color "+fillColor);
    }

}

树枝元素(Composite)- Drawing.java

import java.util.ArrayList;
import java.util.List;

public class Drawing implements Shape{

    //collection of Shapes
    private List<Shape> shapes = new ArrayList<Shape>();
    
    @Override
    public void draw(String fillColor) {
        for(Shape sh : shapes)
        {
            sh.draw(fillColor);
        }
    }
    
    //adding shape to drawing
    public void add(Shape s){
        this.shapes.add(s);
    }
    
    //removing shape from drawing
    public void remove(Shape s){
        shapes.remove(s);
    }
    
    //removing all the shapes
    public void clear(){
        System.out.println("Clearing all the shapes from drawing");
        this.shapes.clear();
    }
}

调用者代码 - TestCompositePattern.java

public class TestCompositePattern {

    public static void main(String[] args) {
        // Draw a atomic shape (Triangle)
        Shape tri0 = new Triangle();
        tri0.draw("Blue");
        
        // Draw a atomic shape (Circle)
        Shape cir0 = new Circle();
        cir0.draw("White");
    
        // Draw a composite shape (2 Triangles and 1 Circle)
        Shape tri = new Triangle();
        Shape tri1 = new Triangle();
        Shape cir = new Circle();
        
        Shape drawing = new Drawing();
        drawing.add(tri1);
        drawing.add(tri1);
        drawing.add(cir);
        
        drawing.draw("Red");
        
        drawing.clear();
        
        drawing.add(tri);
        drawing.add(cir);
        drawing.draw("Green");
    }

}

例子2

基本元素(Base Component)- LetterComposite.java

public abstract class LetterComposite {
  private List<LetterComposite> children = new ArrayList<>();
  public void add(LetterComposite letter) {
    children.add(letter);
  }
  public int count() {
    return children.size();
  }
  protected void printThisBefore() {}
  protected void printThisAfter() {}
  public void print() {
    printThisBefore();
    for (LetterComposite letter : children) {
      letter.print();
    }
    printThisAfter();
  }
}

叶子元素(Leaf)- Letter.java

public class Letter extends LetterComposite {
  private char c;
  public Letter(char c) {
    this.c = c;
  }
  @Override
  protected void printThisBefore() {
    System.out.print(c);
  }
}

树枝元素(Composite)- Word.java

public class Word extends LetterComposite {
  public Word(List<Letter> letters) {
    for (Letter l : letters) {
      this.add(l);
    }
  }
  @Override
  protected void printThisBefore() {
    System.out.print(" ");
  }
}

树枝元素(Composite)- Sentence.java

public class Sentence extends LetterComposite {
  public Sentence(List<Word> words) {
    for (Word w : words) {
      this.add(w);
    }
  }
  @Override
  protected void printThisAfter() {
    System.out.print(".");
  }
}

辅助调用类

public class Messenger {
  LetterComposite messageFromOrcs() {
    List<Word> words = new ArrayList<>();
    words.add(new Word(Arrays.asList(new Letter('W'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e'))));
    words.add(new Word(Arrays.asList(new Letter('t'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e'))));
    words.add(new Word(Arrays.asList(new Letter('i'), new Letter('s'))));
    words.add(new Word(Arrays.asList(new Letter('a'))));
    words.add(new Word(Arrays.asList(new Letter('w'), new Letter('h'), new Letter('i'), new Letter('p'))));
    words.add(new Word(Arrays.asList(new Letter('t'), new Letter('h'), new Letter('e'), new Letter('r'), new Letter('e'))));
    words.add(new Word(Arrays.asList(new Letter('i'), new Letter('s'))));
    words.add(new Word(Arrays.asList(new Letter('a'))));
    words.add(new Word(Arrays.asList(new Letter('w'), new Letter('a'), new Letter('y'))));
    return new Sentence(words);
  }

  LetterComposite messageFromElves() {
    List<Word> words = new ArrayList<>();
    words.add(new Word(Arrays.asList(new Letter('M'), new Letter('u'), new Letter('c'), new Letter('h'))));
    words.add(new Word(Arrays.asList(new Letter('w'), new Letter('i'), new Letter('n'), new Letter('d'))));
    words.add(new Word(Arrays.asList(new Letter('p'), new Letter('o'), new Letter('u'), new Letter('r'), new Letter('s'))));
    words.add(new Word(Arrays.asList(new Letter('f'), new Letter('r'), new Letter('o'), new Letter('m'))));
    words.add(new Word(Arrays.asList(new Letter('y'), new Letter('o'), new Letter('u'), new Letter('r'))));
    words.add(new Word(Arrays.asList(new Letter('m'), new Letter('o'), new Letter('u'), new Letter('t'), new Letter('h'))));
    return new Sentence(words);
  }
}

调用

LetterComposite orcMessage = new Messenger().messageFromOrcs();
orcMessage.print(); // Where there is a whip there is a way.
LetterComposite elfMessage = new Messenger().messageFromElves();
elfMessage.print(); // Much wind pours from your mouth.

优缺点

优点

待补充

缺点

待补充

适用场景

  • 希望表达一个具有层次结构的类(part-whole hierarchies of related objects),如包含多个部分且包含不同题目类型的一份考试题目,包含多个部分(section)和多种不同的题目类型(选择题、问答题等)
  • 复合对象和原子对象对于调用者来说,都是一样的对象,如复合题目(包含多道题目),和一个单选题目,对于调用者来说都是题目对象

应用

Java AWT中* java.awt.Container and java.awt.Component。

在Java AWT中,Component类是抽象构件,Checkbox、Button和TextComponent是叶子构件,而Container是容器构件(当然,在AWT中包含的叶子构件还有很多)。

在一个容器构件中可以包含叶子构件,也可以继续包含容器构件,这些叶子构件和容器构件一起组成了复杂的GUI界面。

Reference