欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

设计模式---组合模式

程序员文章站 2022-03-10 17:21:02
1. 定义大名鼎鼎的套娃模式组合模式有时又称部分-整体模式(Part-Whole)将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。——《设计模式》GoF用来描述整体与部分的关系将对象组织到树结构中可以使客户端将单个对象与组合对象统一处理。对象的树结构一个树结构由两种节点组成:树枝节点和树叶节点。树枝节点可以有子节点,而一个树叶节点不可以有子节点。除了根节点外,其它节点有且只有一个父节点。2. 为什么要用组合模式...

1. 定义

大名鼎鼎的套娃模式

组合模式有时又称部分-整体模式(Part-Whole)
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
——《设计模式》GoF

  • 用来描述整体与部分的关系
  • 将对象组织到树结构中
  • 可以使客户端将单个对象与组合对象统一处理。
  • 对象的树结构
    一个树结构由两种节点组成:树枝节点和树叶节点。树枝节点可以有子节点,而一个树叶节点不可以有子节点。除了根节点外,其它节点有且只有一个父节点。

2. 为什么要用组合模式

  • 对象容器的问题(对象树中的分支节点)
    在面向对象系统中,我们常会遇到一类具有“容器”特征的对象——即它们在充当对象的同时,又是其他对象的容器.。

设计模式---组合模式

  • 在客户端往往需要对容器对象进行复杂的处理:

设计模式---组合模式

  • 前面的客户代码里面必须要知道组合对象的结构,有可能还要使用递归的方法来处理这个对象,这样写耦合性就比较高。客户代码如果能只和IBox发生依赖就会好多了,但是现在它还和ContainerBox和SingleBox发生了依赖,这样2个具体的Box类内部实现的细节就暴露给了外界,并且和外界产生了依赖关系。
  • 上述问题根源在于:客户代码过多地依赖于具体对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象接口)的变化将引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。如何将“客户端代码与复杂的对象容器结构”解耦?让对象容器自己来实现自身的复杂结构(SRP),从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

3. 两种解决方案

3.1 整体架构

  • 抽象构件(Component)角色:这是一个抽象角色,它给参与组合的对象规定一个接口。这个角色给出共有接口及其默认行为。
  • 树叶构件(Leaf)角色:代表参加组合的树叶对象。一个树叶对象没有下级子对象。
  • 树枝构件(Composite)角色:代表参加组合的有子对象的对象,给出树枝构件对象的行为。Composite类型的对象可以包含其它Component类型的对象。换而言之,Composite类型对象可以含有其它的树枝(Composite)类型或树叶(Leaf)类型的对象。

设计模式---组合模式

组合模式的实现根据所实现接口的区别分为两种形式,分别称为安全模式和透明模式。

3.2 安全模式

安全式的组合模式要求管理组合对象的方法只出现在树枝构件类中,而不出现在树叶构件中。

组合模式可以不提供父对象的管理方法,但组合模式必须在合适的地方提供子对象的管理方法(诸如:add、remove、getChild等)

在Composite类里面声明所有的用来管理子类对象的方法。这样的做法是安全的做法,因为树叶类型的对象根本就没有管理子类对象的方法,因此,如果客户端对树叶类对象使用这些方法时,程序会在编译时期出错。
这个选择的缺点是不够透明,因为树叶类和组合类将具有不同的接口。

设计模式---组合模式

  • 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。
  • 树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。
  • 树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝对象给出所有的管理子对象的方法,如add()、remove()、getChild()等。
namespace SafeComposite
{
    public abstract class Component
    {
        // Fields
        protected string name;

        // Constructors
        public Component(string name)
        {
            this.name = name;
        }

        // Operation
        public abstract void Display(int depth);
    }

}

namespace SafeComposite
{
    class Composite : Component
    {
        // Fields
        private ArrayList children = new ArrayList();

        // Constructors
        public Composite(string name) : base(name) { }


        // Methods for managing--分支节点专有的方法
        public void Add(Component component)
        {
            children.Add(component);
        }
        public void Remove(Component component)
        {
            children.Remove(component);
        }


        public override void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + name);

            // Display each of the node's children
            foreach (Component component in children)
                component.Display(depth + 2);
        }
    }

}

namespace SafeComposite
{
    class Leaf : Component
    {
        // Constructors
        public Leaf(string name) : base(name) { }

        // Methods
        public override void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + name);
        }
    }

}

namespace SafeComposite
{
    class Program
    {
        static void Main(string[] args)
        {
            // 注意此处不能使用Component,因为它不能对子节点进行管理操作
            Composite root = new Composite("root");
            root.Add(new Leaf("Leaf A"));
            root.Add(new Leaf("Leaf B"));
            Composite comp = new Composite("Composite X");

            comp.Add(new Leaf("Leaf XA"));
            comp.Add(new Leaf("Leaf XB"));
            root.Add(comp);

            root.Add(new Leaf("Leaf C"));

            // Add and remove a leaf
            Leaf l = new Leaf("Leaf D");
            //l.Add(new Leaf("xxxxxx"));    //Compiling Error!!!
            root.Add(l);
            root.Remove(l);

            // Recursively display nodes
            root.Display(0);

            Console.ReadLine();
        }
    }
}

3.3 透明模式

透明式的组合模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定的接口。

在Component里面声明所有的用来管理子类对象的方法,包括add()、remove(),以及getChild()方法。这样做的好处是所有的构件类都有相同的接口。在客户端看来,树叶类对象与组合类对象的区别起码在接口层次上消失了,客户端可以同等的对待所有的对象。
这种方式的缺点是不够安全,因为树叶类对象和组合类对象在本质上是有区别的。树叶类对象不可能有下一个层次的对象,因此add()、remove()以及getChild()方法没有意义,这样做即便在编译时期不会出错,但是在运行时期会出错。

设计模式---组合模式

  • 抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象规定一个接口,规范共有的接口及默认行为。
  • 树叶构件(Leaf)角色:代表参加组合的树叶对象,定义出参加组合的原始对象的行为。树叶类会给出add()、remove()以及getChild()之类的用来管理子类对对象的方法的平庸实现。
  • 树枝构件(Composite)角色:代表参加组合的有子对象的对象,定义出这样的对象的行为。
namespace TransparentComposite
{
    public abstract class Component
    {
        // Fields
        protected string name;

        // Constructors
        public Component(string name)
        { this.name = name; }


        // 子类对象的管理方法
        abstract public void Add(Component c);
        abstract public void Remove(Component c);

        //与业务相关的操作
        abstract public void Display(int depth);
    }

}

namespace TransparentComposite
{
    class Composite : Component
    {
        // Fields
        private ArrayList children = new ArrayList();

        // Constructors
        public Composite(string name) : base(name) { }

        // Methods
        public override void Add(Component component)
        { children.Add(component); }

        public override void Remove(Component component)
        { children.Remove(component); }

        public override void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + name);

            // Display each of the node's children
            foreach (Component component in children)
                component.Display(depth + 2);
        }
    }

}
namespace TransparentComposite
{
    class Leaf : Component
    {
        // Constructors
        public Leaf(string name) : base(name) { }

        // Methods
        public override void Add(Component c)
        { Console.WriteLine("Cannot add to a leaf"); }
        public override void Remove(Component c)
        { Console.WriteLine("Cannot remove from a leaf"); }

        public override void Display(int depth)
        { Console.WriteLine(new String('-', depth) + name); }
    }

}

namespace TransparentComposite
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create a tree structure
            Component root = new Composite("root");
            root.Add(new Leaf("Leaf A"));
            root.Add(new Leaf("Leaf B"));
            Component comp = new Composite("Composite X");

            comp.Add(new Leaf("Leaf XA"));
            comp.Add(new Leaf("Leaf XB"));
            root.Add(comp);

            root.Add(new Leaf("Leaf C"));

            // Add and remove a leaf
            Component l = new Leaf("Leaf D");
            l.Add(new Leaf("xxxx"));  //Executing Error!!!
            root.Add(l);
            root.Remove(l);

            // Recursively display nodes
            root.Display(0);

            Console.ReadLine();
        }
    }
}

4. 总结

  • Composite模式采用树形结构来实现普遍存在的对象容器,从而将“一对多”的关系转化为“一对一”的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。

  • 将“客户端代码与复杂的对象容器结构”解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复杂内部实现结构——发生依赖关系,从而更能“应对变化”。

  • Composite模式中,是将“Add和Remove等和对象容器相关的方法”定义在“表示抽象对象的Component类”中,还是将其定义在“表示对象容器的Composite类”中,是一个关乎“透明性”和“安全性”的两难问题,需要仔细权衡。透明方式违背面向对象的“单一职责原则”,但是对于这种特殊结构,这又是必须付出的代价。

  • 微软在.Net中采用了另外的方式来处理这个两难问题: Controls
    示例: DotNetComposite

namespace DotNetComposite
{
    /// <summary>
    /// 抽象节点
    /// </summary>
    abstract class Component
    {
        /// <summary>
        /// 子节点列表
        /// </summary>
        public List<Component> Children;//利用此列表来屏蔽安全性和透明性问题--客户端程序直接对此表进行添加和删除

        // Fields
        protected string name;

        // Constructors
        public Component(string name)
        {
            this.name = name;
            Children = new List<Component>();//创建子节点列表--叶子和分支统一对待--区别是叶子节点的列表肯定为空
        }

        // Operation
        public abstract void Display(int depth);
    }
}

namespace DotNetComposite
{
    /// <summary>
    /// 分支节点
    /// </summary>
    class Composite:Component
    {
        // Constructors
        public Composite(string name) : base(name) { }

        public override void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + name);

            //此处需要判断是否含有字节点--有的话,需要依次处理
            if(Children.Count>0)
                foreach (Component component in Children)
                    component.Display(depth + 2);
        }
    }
}
namespace DotNetComposite
{
    /// <summary>
    /// 叶子节点
    /// </summary>
    class Leaf:Component
    {
        // Constructors
        public Leaf(string name) : base(name) { }

        //此处只处理叶子节点的数据,不会理会是否含有子节点(应该没有子节点)
        public override void Display(int depth)
        {
            Console.WriteLine(new String('-', depth) + name);
        }
    }
}

namespace DotNetComposite
{
    class Program
    {
        static void Main(string[] args)
        {
            Component root = new Composite("root");
            root.Children.Add(new Leaf("Leaf A"));
            root.Children.Add(new Leaf("Leaf B"));
            Component comp = new Composite("Composite X");

            comp.Children.Add(new Leaf("Leaf XA"));
            comp.Children.Add(new Leaf("Leaf XB"));
            root.Children.Add(comp);

            root.Children.Add(new Leaf("Leaf C"));

            // Add and remove a leaf
            Leaf l = new Leaf("Leaf D");
            l.Children.Add(new Leaf("xxxxxx"));    //此处添加子节点无意义--添加不会出任何问题,但是无用--处理方法中不含有处理子节点的代码
            root.Children.Add(l);
            root.Children.Remove(l);

            // Recursively display nodes
            root.Display(0);

            Console.ReadLine();
        }
    }
}

可以看出,将Composite类中的集合类型的数据结构放在抽象类中,使叶子类也具有这个集合,但是增删子节点对叶子类不作任何操作,可以做到在客户端透明操作,又不会报错。

本文地址:https://blog.csdn.net/qq_41342326/article/details/107481091

相关标签: # 设计模式