动机
- 希望表达具有层次结构(部分-整体,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
- 《Design Patterns: Elements of Reusable Object-Oriented Software》
- 《Java与模式》
- Journaldev – Composite Design Pattern in Java - https://www.journaldev.com/1535/composite-design-pattern-in-java
- https://github.com/iluwatar/java-design-patterns/tree/master/composite
- https://quanke.gitbooks.io/design-pattern-java/content/%E6%A0%91%E5%BD%A2%E7%BB%93%E6%9E%84%E7%9A%84%E5%A4%84%E7%90%86%E2%80%94%E2%80%94%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F%EF%BC%88%E4%BA%94%EF%BC%89.html