当前位置: 移动技术网 > IT编程>软件设计>设计模式 > 设计模式复习(九)-------组合模式

设计模式复习(九)-------组合模式

2020年07月23日  | 移动技术网IT编程  | 我要评论

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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网