C# 设计模式之组合模式

总目录


前言

日常使用电脑过程中,经常接触到文件夹和文件,我们知道在文件系统中,文件夹可以包含文件夹,且可以嵌套多层,最里面包含的是文件。文件作为最底层对象,里面是不可以再包含文件夹和文件的。类似于文件目录这样的结构,可以称之为树形结构。而树形结构中的对象可以分为两类,一类是:容器对象(复合对象),可以包含其他的子对象;另一类是:叶子对象(简单对象),这类对象是不能在包含其他对象的对象。由于简单对象和复合对象在功能上区别,导致在操作过程中必须区分简单对象和复合对象,这样就会导致客户调用带来不必要的麻烦,作为客户,它们希望能够始终一致地对待简单对象和复合对象。对于这样的问题,让我们看看组合模式是怎样解决这个问题的呢?


1 基础介绍

  1. 组合模式允许你将对象组合成树形结构来表现”部分-整体“的层次结构,使得客户以一致的方式处理单个对象和组合对象

  2. 组合模式实现的最关键的地方是:简单对象和复合对象必须实现相同的接口。这就是组合模式能够将组合对象和简单对象进行一致处理的原因。

  3. 组合模式中的角色:

    • 抽象构件角色(Component):它可以是接口或抽象类,为节点组件和容器组件对象声明接口,在该类中包含共有行为的声明。在抽象组件类中,定义了访问和管理它的子组件的方法(在透明式组合模式中是这样)。在安全式的组合模式中,构件角色并不定义出管理子对象的方法,这一定义由树枝结构对象给出。
    • 树叶构件角色(Leaf):节点对象为最小组件(可以理解为树叶),并继承自抽象构件类,实现其共有声明和方法。
    • 树枝构件角色或称容器构件类(Composite):容器对象可以包含无数节点对象和无数容器组件(可以理解为树枝,可以有无数树叶或者分支),容器对象需要实现管理子对象的方法,如Add、Remove等。

2 使用场景

当发现需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式了。

3 实现方式

组合模式有两种实现方式:

  • 透明式的组合模式
  • 安全式的组合模式。

何为“透明式”,何为“安全式”?

透明式是指“抽象构件角色”定义的接口行为集合包含两个部分,一部分是叶子对象本身所包含的行为(比如Operation),另外一部分是容器对象本身所包含的管理子对象的行为(Add,Remove)。这个抽象构件必须同时包含这两类对象所有的行为,客户端代码才会透明的使用,无论调用容器对象还是叶子对象,接口方法都是一样的,这就是透明,针对客户端代码的透明,但是也有他自己的问题,叶子对象不会包含自己的子对象,为什么要有Add,Remove等类似方法呢,调用叶子对象这样的方法可能(注意:我这里说的是可能,因为有些人会把这些方法实现为空,不做任何动作,当然也不会有异常抛出了,不要抬杠)会抛出异常,这样就不安全了,然后人们就提出了“安全式的组合模式”。所谓安全式是指“抽象构件角色”只定义叶子对象的方法,确切的说这个抽象构件只定义两类对象共有的行为,然后容器对象的方法定义在“树枝构件角色”上,这样叶子对象有叶子对象的方法,容器对象有容器对象的方法,这样责任很明确,当然调用肯定不会抛出异常了。

1. 透明式的组合模式

现在我们分析,在文件系统中存在的文件夹和文件 两个对象,常用的操作无非就是
1 添加文件夹或文件
2 移除文件夹或文件
3 打开文件夹或文件
那么我们对外提供的就是这三个接口,这就是容器对象(复合对象)和叶子对象(简单对象)统一的操作接口。

定义抽象类,规范对外提供的接口,这里就是定义文件系统的抽象类

    //文件系统的抽象类
    //负责定义容器对象和简单对象统一对象提供的接口
    public abstract class AbstractFolder 
    {
        // 添加文件夹或文件
        public abstract void Add(AbstractFolder folder);
        //删除文件夹或文件
        public abstract void Remove(AbstractFolder folder);
        //打开文件夹或文件
        public abstract void Open();
    }

定义叶子对象,实现抽象类的接口,这是就是文件

    public class File : AbstractFolder
    {
    	//由于文件是叶子对象,是无法再向文件内添加/移除 文件夹或文件的,因此此处通过抛异常处理
        public override void Add(AbstractFolder folder)
        {
            throw new Exception("无法向文件中添加文件夹或文件");
        }

        public override void Remove(AbstractFolder folder)
        {
            throw new Exception("无法向文件中移除文件夹或文件");
        }
        public override void Open()
        {
            Console.WriteLine("打开文件!");
        }
    }

定义容器对象,同样实现文件抽象类的接口,这里就是文件夹

    public class Folder : AbstractFolder
    {
        public override void Add(AbstractFolder folder)
        {
            Console.WriteLine("添加文件夹或者文件");
        }

        public override void Open()
        {
            Console.WriteLine("打开文件价!");
        }

        public override void Remove(AbstractFolder folder)
        {
            Console.WriteLine("移除文件夹或者文件");
        }
    }

客户端调用:

        static void Main(string[] args)
        {
            AbstractFolder file = new File();
            file.Open();
            //file.Add(new Folder());//会抛异常
            //file.Remove(new Folder());//会抛异常

            AbstractFolder folder = new Folder();
            folder.Open();
            folder.Remove(new Folder());
            folder.Add(new Folder());
        }

我们发现:文件其实是不需要 Add与Remove 方法的,但是为了保持文件和文件夹一致,对外提供一致的接口,因此文件就一同继承Add与Remove方法,并在方法中做出抛异常的处理。优点是让我们清晰透明的知道其中的实现过程,缺点就是对于客户端的调用并不安全,正因如此就有了安全式的组合模式。

2. 安全式的组合模式

1 定义文件抽象类

	//文件抽象类中不再定义Add和Remove方法
	//而是只定义,文件夹和文件都有的接口Open
 	public abstract class AbstractFolder 
    {
        //打开文件夹或文件
        public abstract void Open();
    }

	// 文件类,也只需实现一个Open方法即可
    public class File : AbstractFolder
    {
        public override void Open()
        {
            Console.WriteLine("打开文件!");
        }
    }

	//将文件夹特有的操作,定义在文件夹实现类中
    public class Folder : AbstractFolder
    {
        public void Add(AbstractFolder folder)
        {
            Console.WriteLine("添加文件夹或者文件");
        }

        public override void Open()
        {
            Console.WriteLine("打开文件价!");
        }

        public void Remove(AbstractFolder folder)
        {
            Console.WriteLine("移除文件夹或者文件");
        }
    }

客户端调用:

        static void Main(string[] args)
        {
            AbstractFolder file = new File();
            file.Open();

            Folder folder = new Folder();
            folder.Open();
            folder.Remove(new Folder());
            folder.Add(new Folder());
        }

安全式 组合模式:将文件夹和文件共有的Open 行为,定义在了抽象类中,将文件夹自己需要的Add 和Remove行为定义在文件夹自己的类中!

4 优缺点分析

优点:

  • 组合模式使得客户端代码可以一致地处理单个对象和组合对象
  • 将”客户代码与复杂的对象容器结构“解耦。
  • 可以更容易地往组合对象中加入新的构件。

缺点:

  • 使得设计更加复杂。客户端需要花更多时间理清类之间的层次关系。(这个是几乎所有设计模式所面临的问题)。

结语

希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
C#设计模式(10)——组合模式(Composite Pattern)
C#设计模式之九组合模式(Composite Pattern)【结构型】
c#中组合模式详解

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值