设计模式(17):结构型-组合模式(Composite)

设计模式是软件工程的基石,分为创建型、结构型和行为型三大类。组合模式是结构型模式之一,它将对象组合成树形结构以表达部分-整体关系,使得客户端对单个对象和组合对象的使用具有一致性。该模式在文件系统、绘图和算术表达式等场景中应用广泛。组合模式包含抽象构件、叶子构件和容器构件三个角色,其中抽象构件定义接口,叶子构件实现接口,容器构件同时包含子组件并实现接口。
摘要由CSDN通过智能技术生成

**设计模式(Design pattern)**是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

设计模式分为三种类型,共23种。
创建型模式(5):单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
结构型模式(7):适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
行为型模式(11):(父子类)策略模式、模版方法模式,(两个类)观察者模式、迭代器模式、职责链模式、命令模式,(类的状态)状态模式、备忘录模式,(中间类) 访问者模式、中介者模式、解释器模式。
按alphabeta排列简介如下。
Abstract Factory(抽象工厂模式):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
Adapter(适配器模式):将一个类的接口转换成客户希望的另外一个接口。A d a p t e r模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
Bridge(桥接模式):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
Builder(建造者模式):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
Chain of Responsibility(职责链模式):为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
Command(命令模式):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
Composite(组合模式):将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
Decorator(装饰模式):动态地给一个对象添加一些额外的职责。就扩展功能而言, 它比生成子类方式更为灵活。
Facade(外观模式):为子系统中的一组接口提供一个一致的界面, F a c a d e模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
Factory Method(工厂模式):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
Flyweight(享元模式):运用共享技术有效地支持大量细粒度的对象。
Interpreter(解析器模式):给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
Iterator(迭代器模式):提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
Mediator(中介模式):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
Memento(备忘录模式):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
Observer(观察者模式):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
Prototype(原型模式):用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
Proxy(代理模式):为其他对象提供一个代理以控制对这个对象的访问[1]。
Singleton(单例模式):保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式是最简单的设计模式之一,但是对于Java的开发者来说,它却有很多缺陷。在本月的专栏中,David Geary探讨了单例模式以及在面对多线程(multithreading)、类装载器(classloaders)和序列化(serialization)时如何处理这些缺陷。[2]State(状态模式):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
Strategy(策略模式):定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
Template Method(模板方法模式):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
Visitor(访问者模式):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
#一.概述
##定义

组合模式(Composite Pattern):组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“整体—部分”(Part-Whole)模式,它是一种对象结构型模式。
  Composite Pattern:Compose objects into tree structure to represent part-whole hierarchies.Composite lets clients treat individual objects and compositions of objects uniformly.

将对象组合成树形结构以表示“部分-整体”的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。[GOF 《设计模式》]

##结构
在这里插入图片描述

Component(抽象构件) : 它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。

Leaf(叶子构件) : 它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。

Composite(容器构件) : 它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

##应用场景
1)文件系统由目录和文件组成。每个目录都可以装内容。目录的内容可以是文件,也 可以是目录。
2)算术表达式包括操作数、操作符和另一个操作数。操作数可以是数字,也可以是算术表达式

#二.示例
##1.绘图
这里我们用绘图这个例子来说明Composite模式,通过一些基本图像元素(直线、圆等)以及一些复合图像元素(由基本图像元素组合而成)构建复杂的图形树。在设计中我们对每一个对象都配备一个Draw()方法,在调用时,会显示相关的图形。可以看到,这里复合图像元素它在充当对象的同时,又是那些基本图像元素的一个容器。
绘图结构图
绘图结构图
示例代码(Java)

public abstract class Graphics
{
    protected string _name;

    public Graphics(string name)
    {
        this._name = name;
    }
    public abstract void Draw();
    public abstract void Add();
    public abstract void Remove();
}

public class Picture extends Graphics
{
    protected ArrayList picList = new ArrayList();

    public Picture(string name)
    { super(name);}
    public override void Draw()
    {
        System.println.out("Draw a" + _name.toString());

        for(Graphics g : picList)
        {
            g.Draw();
        }
    }

    public override void Add(Graphics g)
    {
        picList.Add(g);
    }
    public override void Remove(Graphics g)
    {
        picList.Remove(g);
    }
}

public class Line extends  Graphics
{
    public Line(string name)
    { super(name);}

    public override void Draw()
    {
        System.println.out("Draw a" + _name.toString());
    }
    public override void Add(Graphics g)
    { }
    public override void Remove(Graphics g)
    { }
}

public class Circle extends  Graphics
{
    public Circle(string name)
    { super(name);}

    public override void Draw()
    {
        System.println.out("Draw a" + _name.toString());
    }
    public override void Add(Graphics g)
    { }
    public override void Remove(Graphics g)
    { }
}

public class Rectangle extends Graphics
{
    public Rectangle(string name)
    {super(name); }

    public override void Draw()
    {
        System.println.out("Draw a" + _name.toString());
    }
    public override void Add(Graphics g)
    { }
    public override void Remove(Graphics g)
    { }
}

这样引入Composite模式后,客户端程序不再依赖于复合图像元素的内部实现了。然而,我们程序中仍然存在着问题,因为Line,Rectangle,Circle已经没有了子对象,它是一个基本图像元素,因此Add(),Remove()的方法对于它来说没有任何意义,而且把这种错误不会在编译的时候报错,把错误放在了运行期,我们希望能够捕获到这类错误,并加以处理,稍微改进一下我们的程序:

public class Line : Graphics
{
    ......
    public override void Add(Graphics g)
    { 
        //抛出一个我们自定义的异常
    }
    public override void Remove(Graphics g)
    {
        //抛出一个我们自定义的异常
    }
}

这样改进以后,我们可以捕获可能出现的错误,做进一步的处理。上面的这种实现方法属于透明式的Composite模式,如果我们想要更安全的一种做法,就需要把管理子对象的方法声明在树枝构件Picture类里面,这样如果叶子节点Line,Rectangle,Circle使用这些方法时,在编译期就会出错,看一下类结构图:

安全式

相应的示例代码中接口和叶子节点对象中的Add()等方法去掉。

这种方式属于安全式的Composite模式,在这种方式下,虽然避免了前面所讨论的错误,但是它也使得叶子节点和树枝构件具有不一样的接口。这种方式和透明式的Composite各有优劣,具体使用哪一个,需要根据问题的实际情况而定。通过Composite模式,客户程序在调用Draw()的时候不用再去判断复杂图像元素中的子对象到底是基本图像元素,还是复杂图像元素,看一下简单的客户端调用:

public class App
{
    public static void main(String[] args)
    {
        Picture root = new Picture("Root");

        root.Add(new Line("Line"));
        root.Add(new Circle("Circle"));

        Rectangle r = new Rectangle("Rectangle");
        root.Add(r);

        root.Draw();
    }
}

下面用Java实现,与上面有些区别,但更好理解:

package composite;

public class GraphicsApp {
	public static void main(String[] args) {
		Graphics cup = new Picture("杯子",new Line(),new Line(),new Circle());//杯子 由线和圈组成
		Graphics desk = new Picture("凳子",new Line(),new Line(),new Rectangle(),new Rectangle());//桌子
		Graphics chair = new Picture("椅子",new Line(),new Circle(),new Rectangle());//椅子
		Graphics office = new Picture("办公室",cup,desk,desk,chair);  //办公室
		office.Draw();
	}
}	
interface Graphics {
    void Draw();
}
class Line implements Graphics{
	@Override
	public void Draw() {
		System.out.println("Draw a Line");
	}
}
class Circle implements Graphics{
	@Override
	public void Draw() {
		System.out.println("Draw a Circle");
	}
}
class Rectangle implements Graphics{

	@Override
	public void Draw() {
		System.out.println("Draw a Rectangle");
	}
}
//组合对象 
class Picture implements Graphics{
	private Graphics[] gArray;
	private String name;
	public Picture(String name,Graphics... gs) {
		gArray = gs;
		this.name = name;
	}
	@Override
	public void Draw() {
		System.out.println("Draw a "+name+":");
		for(Graphics g: gArray) {
			g.Draw();
		}
	}
}

##2.算术表达式
算术表达式可以简单理解为由操作数、操作符和圆括号组成的式子。操作数又可以为数字或算术表达式。结构类图:
这里写图片描述
此图片来源于中南大学某试题答案
上图中,Operand类为操作数类(叶子类,无子节点)
ArithmeticExpression为算术表达式类(组合类),属性operandA(operandB)为Expression类型,即可以是Opeand或ArithmeticExpression.属性operator为操作符(’+’,’-’,’*’,’/’).

示例代码(Java):

public class CompositeTest {
	public static void main(String[] args) {
		//计算 表达式的值 2+3*5-6/2
		//3*5
		Expression exp1 = new ArithmeticExpression(new Operand(3),new Operand(5), Operator.MULTIPLY);
		//6/2
		Expression exp2 = new ArithmeticExpression(new Operand(6),new Operand(2),Operator.DIVIDE);
		//2+exp1
		Expression exp3 = new ArithmeticExpression(new Operand(2), exp1, Operator.PLUS);
		//exp3-exp2
		Expression exp = new ArithmeticExpression(exp3,exp2, Operator.MINUS);
		System.out.println("2+3*5-6/2="+exp.calculation());
	}
	
}
abstract class Expression{
	public abstract double calculation();
}
class Operand extends Expression{
	private double operand;
	public Operand(double operand){
		this.operand = operand;
	}
	@Override
	public double calculation() {
		return operand;
	}
}
class ArithmeticExpression extends Expression{
	private Expression operandA;
	private Expression operandB;
	private Operator operator;
	public ArithmeticExpression(Expression operandA,Expression operandB,Operator oper){
		this.operandA = operandA;
		this.operandB = operandB;
		this.operator = oper;
	}
	@Override
	public double calculation() {
		switch(operator){
			case PLUS:
				return operandA.calculation()+operandB.calculation();
			case MINUS:
				return operandA.calculation()-operandB.calculation();
			case MULTIPLY:
				return operandA.calculation()*operandB.calculation();
			case DIVIDE:
				return operandA.calculation()/operandB.calculation();
		}
		return 0;
	}
}
enum Operator{
	 PLUS,MINUS,MULTIPLY,DIVIDE;
}

##3.树
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:

这里写图片描述

public class TreeNode {  
      
    private String name;  
    private TreeNode parent;  
    private Vector<TreeNode> children = new Vector<TreeNode>();  
      
    public TreeNode(String name){  
        this.name = name;  
    }  
	...省略 name 和 parent的get,set方法
      
    //添加孩子节点  
    public void add(TreeNode node){  
        children.add(node);  
    }  
      
    //删除孩子节点  
    public void remove(TreeNode node){  
        children.remove(node);  
    }  
      
    //取得孩子节点  
    public Enumeration<TreeNode> getChildren(){  
        return children.elements();  
    }  
}  
public class Tree {  
  
    public static void main(String[] args) {  
        TreeNode tree = new TreeNode("A");  
        TreeNode nodeB = new TreeNode("B");  
        TreeNode nodeC = new TreeNode("C");  
          
        nodeB.add(nodeC);  
        tree.add(nodeB);  
        System.out.println("build the tree finished!");  
    }  
}  

JDK中的文件类java.io.File,它就是一种树型结构定义,也包含一些类似的方法如:
public File getParentFile()
public File[] listFiles()

上面部分内容来源于这个网址:C# 组合模式

《道德经》第二十章:
唯之与阿,相去几何?美之与恶,相去若何?人之所畏,不可不畏。荒兮,其未央哉!众人熙熙,如享太牢,如春登台。我独泊兮,其未兆;沌沌兮,如婴儿之未孩;傫傫兮,若无所归。众人皆有余,而我独若遗。我愚人之心也哉!俗人昭昭,我独昏昏。俗人察察,我独闷闷。澹兮,其若海;飂兮,若无止。众人皆有以,而我独顽且鄙。我独异于人,而贵食母。
*译文:*应诺和呵斥,相距有多远?美好和丑恶,又相差多少?人们所畏惧的,不能不畏惧。这风气从远古以来就是如此,好像没有尽头的样子。众人都熙熙攘攘、兴高采烈,如同去参加盛大的宴席,如同春天里登台眺望美景。而我却独自淡泊宁静,无动于衷。混混沌沌啊,如同婴儿还不会发出嘻笑声。疲倦闲散啊,好像浪子还没有归宿。众人都有所剩余,而我却像什么也不足。我真是只有一颗愚人的心啊!众人光辉自炫,唯独我迷迷糊糊;众人都那么严厉苛刻,唯独我这样淳厚宽宏。恍惚啊,像大海汹涌;恍惚啊,像飘泊无处停留。世人都精明灵巧有本领,唯独我愚昧而笨拙。我唯独与人不同的,关键在于得到了“道”。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值