一、组合模式介绍:

组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以向处理简单元素一样来处理复杂元素。允许你将对象组合成树形结构来表现”部分-整体“的层次结构,使得客户以一致的方式处理单个对象以及对象的组合。组合模式实现的最关键的地方是——简单对象和复合对象必须实现相同的接口。

二、设计背景:

在软件开发过程中,我们经常会遇到处理简单对象和复合对象的情况,例如对操作系统中目录的处理就是这样的一个例子,因为目录可以包括单独的文件(例如word、txt、excel),也可以包括文件夹,文件夹又是由文件组成的,由于简单对象和复合对象在功能上区别,导致在操作过程中必须区分简单对象和复合对象,这样就会导致客户调用带来不必要的麻烦,然而作为客户,它们希望能够始终一致地对待简单对象和复合对象,然而组合模式就是解决这样的问题。下面就让我们以这个例子来看看组合模式的实现。

三、相关代码:

1、创建文件抽象类

    /// <summary>
    /// 文件抽象类
    /// </summary>
    public abstract class File
    {
        public string Name { get; set; }
        public File(string name)
        {
            this.Name = name;
        }
        public abstract void New();
        public abstract void AddFile(File file);
        public abstract void DeleteFile(File file);
    }

2、创建具体简单对象类(简单对象):Word文档类和Excel文档类

    /// <summary>
    /// 具体简单对象类——Word文档文件
    /// </summary>
    public class Word : File
    {
        public Word(string name) : base(name)
        {
        }

        public override void New()
        {
            Console.WriteLine("新建Word文档:" + Name);
        }

        //因为文件是目录的最小单位,文件下无法创建文件,所以简单对象Word文档文件类里面的AddFolder和RemoveFolder方法没有意义
        public override void AddFile(File file)
        {
            throw new Exception("不能向Word文件创建文件");
        }

        public override void DeleteFile(File file)
        {
            throw new Exception("不能向Word文件删除文件");
        }
    }
    /// <summary>
    /// 具体简单对象类——Excel文档文件
    /// </summary>
    public class Excel : File
    {
        public Excel(string name) : base(name)
        {
        }

        public override void New()
        {
            Console.WriteLine("新建Excel文档:" + Name);
        }

        public override void AddFile(File file)
        {
            throw new Exception("不能向Excel文件创建文件");
        }

        public override void DeleteFile(File file)
        {
            throw new Exception("不能向Excel文件删除文件");
        }
    }

3、创建文件夹类(复合对象):

    /// <summary>
    /// 文件夹类,由一些文件组成
    /// </summary>
    public class Folder : File
    {
        private List<File> file_list = new List<File>();
        public Folder(string name) : base(name)
        {
        }

        /// <summary>
        /// 文件夹创建各种文件
        /// </summary>
        public override void New()
        {
            foreach (var item in file_list)
            {
                item.New();
            }
        }

        public override void AddFile(File file)
        {
            file_list.Add(file);
        }
        
        public override void DeleteFile(File file)
        {
            file_list.Remove(file);
        }
    }

4、调用

        static void Main(string[] args)
        {
            Folder folder = new Folder("一个文件夹和一个Word文档组成的文件夹");
            folder.AddFile(new Word("我是Word文档1"));

            Folder folder2 = new Folder("一个Word文档和一个Excel文档组成的文件夹");
            folder2.AddFile(new Word("我是Word文档2"));
            folder2.AddFile(new Excel("我是Excel文档1"));
            folder.AddFile(folder2);
            Excel excel = new Excel("我是Excel文档2");
            folder.AddFile(excel);

            //查看文件夹
            folder.New();
            Console.ReadKey();

            //移除一个文件再查看文件夹
            folder.DeleteFile(excel);
            folder.New();
            Console.ReadKey();
        }

上面的实现方式称为透明式的组合模式,由于基本文件对象不存在AddFile和DeleteFile方法,上面实现中直接通过抛出一个异常的方式来解决这样的问题的,但是我们想以一种更安全的方式来解决——因为基本文件根本不存在这样的方法,我们是不是可以移除这些方法呢?为了移除这些方法,我们就不得不修改File接口,我们把管理子对象的方法声明放在文件夹对象里面,这样简单对象Word、Excel使用这些方法时在编译时就会出错,这样的一种实现方式我们称为安全式的组合模式。具体代码如下:

    /// <summary>
    /// 文件夹类,由一些文件组成
    /// </summary>
    public class Folder : File
    {
        private List<File> file_list = new List<File>();
        public Folder(string name) : base(name)
        {
        }

        /// <summary>
        /// 文件夹创建各种文件
        /// </summary>
        public override void New()
        {
            foreach (var item in file_list)
            {
                item.New();
            }
        }

        public void AddFile(File file)
        {
            file_list.Add(file);
        }
        
        public void DeleteFile(File file)
        {
            file_list.Remove(file);
        }
    }

四、组合模式的三个角色:

  • 抽象构件(Component)角色:这是一个抽象角色,上面实现中File充当这个角色,在透明式的组合模式里,它给参加组合的对象定义出了公共的接口及默认行为,可以用来管理所有的子对象。在安全式的组合模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝结构对象给出。
  • 树叶构件(Leaf)角色:树叶对象时没有下级子对象的对象,上面实现中Word和Excel充当这个角色,定义出参加组合的原始对象的行为
  • 树枝构件(Composite)角色:代表参加组合的有下级子对象的对象,上面实现中Folder充当这个角色,树枝对象给出所有管理子对象的方法实现,如AddFile、DeleteFile等。

五、使用场景:

  1. 需要表示一个对象整体或部分的层次结构。
  2. 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

六、总结:

优点:

  1. 组合模式使得客户端代码可以一致地处理对象和对象容器,无需关系处理的单个对象,还是组合的对象容器。
  2. 将”客户代码与复杂的对象容器结构“解耦。
  3. 可以更容易地往组合对象中加入新的构件。

缺点:

  1. 使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。