组合模式

组合模式

组合模式属于结构型模式,组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让系统可以统一对待单个对象和组合对象。

组合模式关注那些包含叶子构件和容器构件的结构以及它们的组织形式,在叶子构件中不包含成员对象,而容器构件中可以包含成员对象,这些对象通过递归组合可构成一个树形结构。

组合模式的结构

image.png

  • Component(抽象构建):接口或抽象类,为叶子构件和容器构件提供接口,可以包含所有子类共有行为的声明。在抽象构件中声明了访问以及管理它的子构件的方法。
  • Leaf(叶子构件):叶子节点对象,叶子节点不存在子节点,它只实现了抽象构件中定义的行为。对于其它方法可以采取抛出错误信息来处理。
  • Composite(容器构件):容器节点对象,容器节点包含子节点,子节点可以是叶子节点也可以是容器节点。它提供一个集合用来存储子节点,实现了抽象构件中声明的所有方法。

组合模式的实现

组合模式的实现关键点在于定义了一个既可以表示叶子构件又可以表示容器构件的抽象层,通过对抽象层进行编程,无须知道对象具体是什么构件,可以统一进行处理。其次容器对象与抽象构件之间是聚合关联关系,在容器对象中既可以包含叶子又可以包含容器,以此实现递归组合,形成树形结构。

组合模式设计代码如下

//抽象层
abstract class Component
{
    public abstract void Add(Component c);
    public abstract void Remove(Component c);
    public abstract Component GetChild(int i);
    public abstract void Operation();
}

//叶子节点
class Leaf : Component
{
    //实现这些叶子节点不需要的方法增加实现复杂度
    public override void Add(Component c)
    {
        //...异常处理或错误提示
    }

    public override Component GetChild(int i)
    {
        //...异常处理或错误提示
        return null;
    }

    public override void Remove(Component c)
    {
        //...异常处理或错误提示
    }

    public override void Operation()
    {
        //具体业务方法的实现
        Console.WriteLine("叶子节点具体业务方法的实现");
    }
}

//容器节点
class Composite : Component
{
    private List<Component> m_Childs = new List<Component>();
    public override void Add(Component c)
    {
        this.m_Childs.Add(c);
    }

    public override Component GetChild(int i)
    {
        return this.m_Childs[i];
    }

    public override void Remove(Component c)
    {
        this.m_Childs.Remove(c);
    }

    public override void Operation()
    {
        foreach (var item in this.m_Childs)
            item.Operation();
    }
}

示例场景
使用组合模式设计一个界面控件库,界面控件分为两类,单元控件和容器空间,单元空间包括文本、按钮等,容器空间包括窗体、面板等。

代码

//抽象构件
abstract class AbstractComponent
{
    public abstract void Add(AbstractComponent component);
    public abstract void Remove(AbstractComponent component);
    public abstract AbstractComponent GetChild(int index);
    public abstract void Redraw();
}

//文本控件(叶子节点)
class TextComponent : AbstractComponent
{
    private string m_Name;

    public TextComponent(string name)
    {
        this.m_Name = name;
    }

    public override void Add(AbstractComponent component)
    {
        throw new Exception("文本控件不支持该方法");
    }

    public override AbstractComponent GetChild(int index)
    {
        throw new Exception("文本控件不支持该方法");
    }

    public override void Remove(AbstractComponent component)
    {
        throw new Exception("文本控件不支持该方法");
    }

    public override void Redraw()
    {
        Console.WriteLine("重绘------{0}", this.m_Name);
    }
}

//按钮控件(叶子节点)
class ButtonComponent : AbstractComponent
{
    private string m_Name;

    public ButtonComponent(string name)
    {
        this.m_Name = name;
    }

    public override void Add(AbstractComponent component)
    {
        throw new Exception("按钮控件不支持该方法");
    }

    public override AbstractComponent GetChild(int index)
    {
        throw new Exception("按钮控件不支持该方法");
    }

    public override void Remove(AbstractComponent component)
    {
        throw new Exception("按钮控件不支持该方法");
    }

    public override void Redraw()
    {
        Console.WriteLine("重绘------{0}", this.m_Name);
    }
}

//窗体控件(容器节点)
class FormsComponent : AbstractComponent
{
    private string m_Name;

    private List<AbstractComponent> m_Childs;

    public FormsComponent(string name)
    {
        this.m_Name = name;
        this.m_Childs = new List<AbstractComponent>();
    }

    public override void Add(AbstractComponent component)
    {
        this.m_Childs.Add(component);
    }

    public override AbstractComponent GetChild(int index)
    {
        return this.m_Childs[index];
    }

    public override void Remove(AbstractComponent component)
    {
        this.m_Childs.Remove(component);
    }

    public override void Redraw()
    {
        Console.WriteLine("重绘------{0}", this.m_Name);
        foreach (var item in this.m_Childs)
            item.Redraw();
    }
}

//面板控件(容器节点)
class PanelComponent : AbstractComponent
{
    private string m_Name;

    private List<AbstractComponent> m_Childs;

    public PanelComponent(string name)
    {
        this.m_Name = name;
        this.m_Childs = new List<AbstractComponent>();
    }

    public override void Add(AbstractComponent component)
    {
        this.m_Childs.Add(component);
    }

    public override AbstractComponent GetChild(int index)
    {
        return this.m_Childs[index];
    }

    public override void Remove(AbstractComponent component)
    {
        this.m_Childs.Remove(component);
    }

    public override void Redraw()
    {
        Console.WriteLine("重绘------{0}", this.m_Name);
        foreach (var item in this.m_Childs)
            item.Redraw();
    }
}

//使用
static void Main()
{
    var forms = new FormsComponent("我的窗体");

    var panel1 = new PanelComponent("我的界面");
    var panel2 = new PanelComponent("子界面");

    var text1 = new TextComponent("标题文本");
    var text2 = new TextComponent("内容文本");

    var button1 = new ButtonComponent("关闭按钮");
    var button2 = new ButtonComponent("刷新按钮");

    panel2.Add(text1);
    panel2.Add(text2);
    panel2.Add(button1);

    panel1.Add(panel2);
    panel1.Add(text1);
    panel1.Add(button1);
    panel1.Add(button2);

    forms.Add(panel1);
    forms.Add(text1);
    forms.Add(button1);

    //重绘我的窗体
    forms.Redraw();
    Console.ReadKey();
}

运行结果
image.png

透明组合模式与安全组合模式

透明组合模式

上述所讲的是透明组合模式,在透明组合模式中抽象构件中声明了子类的所有公共方法,这样做的好处是所有子类都具有相同的接口,可以将叶子节点与容器节点统一处理。

其缺点是不够安全,因为叶子节点不存在下一层,和容器节点有本质上的差别,因此为叶子节点提供Add、Remove等方法是没有意义的。

安全组合模式

安全组合模式中,抽象构件将不再声明任何用于管理成员对象的方法,而是在Composite声明这些方法。对于叶子节点将调用不到这些方法,所以这样做是更安全的。

其缺点也很明显,就是不够透明,因为叶子节点与容器节点存在差异,因此不再能够完全针对抽象层编程,必须区别对待叶子节点与容器节点。

安全组合模式结构
image.png

组合模式的优点

  • 能够清楚地定义分层次的复杂对象,表示对象的全部或部分层次,从而使用时可以忽略层次的差异,方便对整个层次结构进行控制。
  • 可以一直地使用一个组合结构或是其中的一个单元,不必关心处理对象的类型,简化代码。
  • 增加新的容器构件与叶子构件都很方便,无需修改现有代码,符合开闭原则。
  • 为树形结构的面向对象提供了一种灵活的解决方案,通过叶子节点和容器节点的递归组合可以形成复杂的树形结构,但是对树形结构的控制非常简单。

组合模式的缺点

在使用组合模式时,其叶子节点和容器节点都是具体的实现类,而不是接口,违反了依赖倒置原则。

应用场景

  • 在具有整体和部分的结构中,希望能够忽略整体与部分的差异进行统一处理。
  • 处理树形结构。
  • 在一个系统中能够分离出叶子节点与容器节点,且它们的类型不固定(后续可能会增加新类型)。
7 声望
3 粉丝
0 条评论
推荐阅读
装饰模式
装饰模式可以在不改变已有对象本身的功能的基础上给对象增加额外的新职责,好比日常生活中的照片,可以给照片使用相框,使之具有防潮的功能,但是这样并没有改变照片本身,这便是装饰模式。

DoubleJ阅读 821

15分钟入门23种设计模式:图解,范例和对比
本文力图在15分钟内,通过UML图解、范例和类比,让你对面向对象的23种设计模式形成提纲挈领的认识,从而让我们在面临代码设计问题时更加成竹在胸。本文源代码: UML, Sample Code。

风云信步5阅读 679评论 1

短小精悍的发布订阅库 mitt
发布-订阅模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。

qinghuanI3阅读 296

封面图
工厂模式、单例模式、策略模式、适配器模式、观察者模式的原理和使用详解
🎈 工厂模式工厂模式的原理作用: 就是你只要传你需要的类进去,你就能得到他的实例化对象其实工厂就是帮你实例化你所需要的类 {代码...} 工厂模式的应用实例化多个类来处理不同业务时候使用,这里以求矩形和圆形...

tiny极客1阅读 661评论 1

封面图
php 中的 DI 依赖注入
🎈 什么是 DI / 依赖注入依赖注入DI 其实本质上是指对类的依赖通过构造器完成 自动注入通俗来说,就是你当前操作一个类,但是这个类的某些方法或者功能不是单单只靠这个类就能完成的,而是要 借助另一个类 的才能...

tiny极客1阅读 632评论 1

封面图
设计模式篇之一文搞懂如何实现单例模式
设计模式篇之一文搞懂如何实现单例模式大家好,我是小简,这一篇文章,6种单例方法一网打尽,虽然单例模式很简单,但是也是设计模式入门基础,我也来详细讲讲。DEMO仓库:[链接] ,欢迎PR,共建。单例模式单例模...

JanYork_小简1阅读 614

设计模式-策略模式
作为一名合格的前端开发工程师,全面的掌握面向对象的设计思想非常重要,而“设计模式”是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的,代表了面向对象设计思想的最佳实践。正如《HeadFirst设计模...

astonishqft阅读 728

封面图
7 声望
3 粉丝
宣传栏