结构型模式之组合模式

目的

将对象组合成树形结构以表示“部分整体”的层次结构,Composite使得用户对单个对象和组合对象的使用具有一致性。

动机

当复杂的组件通过多个简单组件进行组合生成的,一个简单的方法时简单组件定义为元类,由元类组成的类定义为容器类(Container)。
Composite模式描述了使用递归方法,使得用户不必区别对待元对象和容器对象。
组合模式类图图示

Composite模式的关键时一个抽象类,它既可以代表元又可以代表元的容器。
组合对象结构
组合对象结构

适用性

表示对象的部分和整体的层次结构
忽略组合对象和单个对象的不同,用户统一地使用组合结构中的所有对象

参与者

  • Component
    为组合中的对象声明接口。
    在适当的情况下,实现所有类共有接口的缺省行为。
    声明一个接口用于访问和管理Component的子组件。
    在递归结构中定义一个接口,用于访问一个父部件,并在合适的情况下实现它。
  • Leaf
    在组合中表示叶节点对象,叶节点没有子节点。
    在组合中定义图元对象的行为。
  • Composite
    定义有子部件的部件的行为。
    存储子部件。
    在Component接口中实现与子部件有关的操作
  • Client
    通过Component接口操作组合部件的对象。

效果

定义了包含基本对象和组合对象的类层次结构
基本对象可以被组合合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,在客户代码中,任何用到基本对象的地方都可以用到组合对象。

简化客户代码
客户可以一致的使用组合结构和单个对象。在定义组合的类中不需要写选择语句的函数。

使得更容易增加新类型组件
新定义的Composite或Leaf自雷自动的与已有的结构和客户代码在一起工作,客户程序不需要因新的Component类而改变。

使设计更一般化
容易增加新组件也会产生新问题,那就是很难限制组合中的组件。有时你希望一个组合只能有某些特定的组件。使用组合模式时,你不能依赖类型系统施加这些约束,而必须在运行时进行检查。

实现

  1. 显式的父部件引用
    保持从子部件到父部件的引用能简化组合结构的遍历和管理。父部件引用可以简化结构上移和组件删除,同时父部件引用也支持职责链模式(Chain of Responsibility)
    Leaf和Composite类可以继承这个引用以及管理这个引用的操作。
    对于父部件引用,必须维护一个不变式。即一个组合的所有子节点以这个组合为父节点。Composite类的add和remove操作,那么所有的子类都可以继承这一方法,并自动维护这一不变式。
  2. 共享组件
    共享组件可以减少对存储的需求。
    一个可行的解决办法时为子部件储存多个父部件,但当一个请求在结构中向上传递时,这种方法会导致多义性。享元模式(Flyweight)讨论了如何修改设计以避免将父结构储存在一起的方法。如果子部件可以将一些状态存储在外部,从而不需要向父部件发送请求。
  3. 最大化Component接口
    组合模式的目的之一时使得用户不知道他们正在使用的具体的Leaf和Composite类。为了实现这一目的,Component类应尽可能的多定义一些公共操作。Composite类通常提供方法的缺省实现。而Leaf和Composite的子类可以对它们进行重定义。
  4. 声明管理子部件的操作
    1)在composite类中定义子部件管理操作,安全性高,透明性低。(composite和leaf操作不一致性增强,且转型有安全性问题,不推荐)
    提高转型安全性:
    在component类中增加获取composite方法。
    若自身为组合体,返回自身。否则返回空指针。
    2)在Component类定义子部件管理操作,安全性低,透明性高。(leaf存在无意义操作)
    提高安全性:
    在leaf中利用异常,防止误操作
	@Override
    void add(Component component) throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }
    @Override
    void delete() throws UnsupportedOperationException {
        throw new UnsupportedOperationException();
    }
  1. Component是否应该实现Component列表
    叶子类数目很少时,才推荐实现该列表。否则对子结点空间浪费严重。

  2. 子部件排序
    如果需要,设计对子结点的访问和管理接口。

  3. 使用高速缓冲存储
    若对组件遍历查找频繁,Composite类可以缓冲存储对它的子部件的遍历查找相关信息。并且需要实现一个接口判断组合部件起缓冲信息是否有效。

  4. 删除Component
    在没有GC机制的语言中,通常最好由Composite负责删除子结点。但当Leaf为共享组件时,删除leaf时,Leaf对象不会改变。

代码实例

一个关于图形界面的组件实例
Component Class

interface Graphic {

    void add(Graphic graphic) throws OperationNotSupportedException;

    void delete(Graphic graphic) throws OperationNotSupportedException;

    List<Graphic> getChild() throws OperationNotSupportedException;

    void draw();
}

Composite Class

public class Picture implements Graphic {
    List<Graphic> list;

    public void add(Graphic graphic){
        list.add(graphic);
    }

    public void delete(Graphic graphic) {
        list.remove(graphic);
    }

    public void draw(){
        for (Graphic graphic : list) {
            graphic.draw();
        }
    }

    public List<Graphic> getChild(){
        return list;
    }

}

Abstract Leaf Class

public abstract class Element implements Graphic {
    @Override
    public void add(Graphic graphic) throws OperationNotSupportedException {
        throw new OperationNotSupportedException();
    }

    @Override
    public void delete(Graphic graphic) throws OperationNotSupportedException {
        throw new OperationNotSupportedException();
    }

    @Override
    public List<Graphic> getChild() throws OperationNotSupportedException {
        throw new OperationNotSupportedException();
    }
}

Concrete Leaf Class

public class Line extends Element{
    @Override
    public void draw() {
        System.out.println("draw a line");
    }
}
public class Text extends Element {
    @Override
    public void draw() {
        System.out.println("write some letter\n");
    }
}
public class Rectangle extends Element {
    @Override
    public void draw() {
        System.out.println("draw a rectangle\n");
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值