(8)结构型模式——复合

结构型模式——复合(Composite)

问题背景

当希望用某种统一的方式表示一个树形结构,并屏蔽内部实现,提供统一接口时,考虑使用复合模式(也叫组合模式)。复合模式的典型应用场景就是界面,界面包括文字、按钮等基本控件,还包括窗口、板块等用于承载其他控件的容器控件。有了这种结构,用户就可以对界面进行任意组合,并且以统一的方式操作界面。拿界面的一个基本功能举例:用户拖动父控件,改变其位置,子控件也会随之改变位置,这项功能的实现就离不开复合模式。

解决方案

为了给所有控件提供统一操作,我们提取一个控件接口IControl,并提供所有控件都具备的操作。对于基本控件,直接实现IControl接口即可;对于容器控件,有两种选择:一是直接实现IControl接口,并将容器控件特有的操作也加入到IControl中;二是再抽象出一个容器接口IContainer,包含容器控件特有的操作,令IContainer继承IControl,之后所有容器控件实现IContainer。

下面分析一下两种做法的优劣势:

第一种做法的好处是透明性强,即用户不需要关心基本控件和容器控件的区别,一致使用IControl接口即可。坏处是安全性差,由于IControl中多了基本控件不具备的操作,需要在基本控件中对这些操作做特殊处理。或提供一个空实现,或抛出异常,或约定不去调用这些接口。但无论如何,这些做法都无法享受编译期检查带来的方便,一切错误都要在运行时才能被察觉。

第二种做法的好处是安全性强,由于容器控件的特有操作被放到了子接口中,编译期就可以帮助检查基本控件是否调用了容器控件的特有接口。但这种做法破坏了透明性——用户必须区分基本控件和容器控件。

综上所述:做法一是以安全性换取透明性,做法二是以透明性换取安全性。

《设计模式》第110页中有这样一句话:在这一模式中,相对于安全性,我们比较强调透明性。如果你选择了安全性,有时你可能会丢失类型信息,并且不得不将一个组件转换成一个组合。这样的类型转换必定不是类型安全的。

可见作者认为在这一模式中透明性更加重要。但我个人认为,我们不该以绝对的眼光去看待任何事物,在实际操作中,最好科学评估这两个指标在具体系统中的重要性后再做决定。

这里采用做法一,程序结构如下:
程序结构

效果

  1. 定义了复合结构,理论上可以创建出无限复杂的对象。
  2. 简化用户操作,用户操作根节点和操作所有结点是等价的。
  3. 提高了系统的可扩展性,这是面向接口编程的共有优势。

缺陷

上面也提到了,复合模式中的透明性与安全性是鱼与熊掌的关系,必须在二者间作出取舍,这是一方面。另一方面,复合模式带来的透明性异常脆弱,这表现在:实现类不能有任何特殊操作,如果非要为具体实现定义特殊功能,则用户在使用时就必须破坏透明性,将接口类型显示转为实现类型。有句话叫“越高级的结构越脆弱”,说的就是这个意思,复合模式高度透明的条件太过苛刻,导致这种性质非常容易被破坏。

相关模式

  1. 装饰器:装饰器可以配合复合模式来实现动态增强结点的功能。
  2. 享元:可以共享某些组件。
  3. 迭代器:可以按某种规则遍历复合结构。
  4. 访问器:可以将子节点的操作上移到根节点。

实现

using System;
using System.Collections.Generic;

namespace Composite
{
    class Client
    {
        public interface IControl
        {
            bool Add(IControl control);
            bool Remove(IControl control);
            void Move(int dx, int dy);
        }

        public class Label : IControl
        {
            public bool Add(IControl control)
            {
                return false;
            }
            public bool Remove(IControl control)
            {
                return false;
            }
            public void Move(int dx, int dy)
            {
                Console.WriteLine($"Label move ({dx}, {dy})");
            }
        }

        public class Button : IControl
        {
            public bool Add(IControl control)
            {
                return false;
            }
            public bool Remove(IControl control)
            {
                return false;
            }
            public void Move(int dx, int dy)
            {
                Console.WriteLine($"Button move ({dx}, {dy})");
            }
        }

        public class Window : IControl
        {
            private List<IControl> children = new List<IControl>();
            public bool Add(IControl control)
            {
                children.Add(control);
                return true;
            }
            public bool Remove(IControl control)
            {
                return children.Remove(control);
            }
            public void Move(int dx, int dy)
            {
                Console.WriteLine($"Window move ({dx}, {dy})");
                foreach (var item in children)
                {
                    item.Move(dx, dy);
                }
            }
        }

        public class Panel : IControl
        {
            private List<IControl> children = new List<IControl>();
            public bool Add(IControl control)
            {
                children.Add(control);
                return true;
            }
            public bool Remove(IControl control)
            {
                return children.Remove(control);
            }
            public void Move(int dx, int dy)
            {
                Console.WriteLine($"Panel move ({dx}, {dy})");
                foreach (var item in children)
                {
                    item.Move(dx, dy);
                }
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("构造界面...");
            var window = new Window();
            var label1 = new Label();
            var panel = new Panel();
            var label2 = new Label();
            var button = new Button();
            window.Add(label1);
            window.Add(panel);
            panel.Add(label2);
            panel.Add(button);

            Console.WriteLine("移动Window...");
            window.Move(100, 100);

            Console.WriteLine("删除Window下的Label再移动...");
            window.Remove(label1);
            window.Move(200, -500);
        }
    }
}

运行结果

发布了10 篇原创文章 · 获赞 10 · 访问量 480

猜你喜欢

转载自blog.csdn.net/DIAX_/article/details/104168361