组合模式

一、概念

在面向编程中,有一条非常经典的设计原则,那就是:组合优于继承,多用组合少用继承,继承是面向对象的四大特征之一,表示is-a的类关系,继承特性解决了代码复用的问题,但是当继承的深度过度,代码变得更复杂,变得更难以维护。而组成则表示has-a的关系,是把另外一个对象当作当前这个对象的一部分,是组成我的一部分,能够很好的实现代码的复用。

组合模式是一种结构性设计模式,它允许你将对象组合成树形结构,以表示部分-整体的层次结构,这种模式对单个对象(即叶子节点)和组合对象(即容器节点)的使用具有一致性。

定义:组合模式组合多个对象形成树形结构以表示具有部分-整体关系的层次结构,组合模式让客户端可以统一对待单个对象和组合对象。

二、模式结构

2.1 结构

  1. Component(抽象组件):定义了组合中所有对象必须实现的通用接口,可以是抽象类或者接口,它声明了用于访问和管理子组件的方法。包括添加、删除、获取子组件等。

  2. Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了组件接口的方法,但不包含子节点。

  3. Composite(复合构件):表示了组合中的复合对象,复合节点可以包含子节点,也可以是叶子节点,也可以是其他复合节点,它实现了组合接口的方法,包括管理子节点的方法。

    image-20240630220426002

2.2 透明组合模式与安全组合模式

组合模式根据抽象构件类的定义形式又可以分为透明组合模式和安全组合模式。

2.2.1 透明组合模式

在抽象构件Component中声明了所有用于管理成员对象的方法,包括add()、remove()、以及getChild等方法,叶子节点和复合节点所有的方法都是一致的。

image-20240630222236068

透明组合模式缺点就是不够安全,叶子对象和容器对象在本质上是有区别的,叶子对象没有子节点,所有不会包含add、remove、getChild等方法,透明组合模式在编译阶段不会报错,但是在运行阶段调用则会抛出异常。

2.2.2 安全组合模式

在安全组合模式中,抽象构件Component中没有声明任何用于管理成员对象的方法,而是在Composite中声明并实现这些方法,这种方法是安全的。缺点就是不够透明,客户端不能完全针对抽象编程,必须区别对待叶子构件和容器构件。

image-20240630223218202

三、应用实例

以电脑的文件系统为例,利用组合模式来设计文件系统。

  • Component:抽象组件

    public abstract class Component
    {
    
      protected string _name;
      public string Name => _name;
    
      public string Path { get; set; }
      public bool IsDirectory { get; set; }
      public bool IsFile { get; set; }
    
      public Component? parent;
    
      public virtual void Remove(Component component) { }
      public abstract void Display();
      public abstract void Delete();
    }
  • Folder:复合构件

    public class Folder : Component
    {
      public Folder() 
      {
          _name = "新建文件夹";
          IsDirectory = true;
          IsFile = false;
      }
    
      public Folder(string name) 
      {
          _name = name;
          IsDirectory = true;
          IsFile = false;
      }
    
      public List children = new List();
    
      public void Add(Component component) 
      {
          component.Path = $"{Path}/{component.Name}";
          component.parent = this;
          children.Add(component);
      }
    
      public override void Display()
      {
          Console.WriteLine($"Name: {_name} Path: {Path}");
          foreach (Component component in children)
          {
              component.Display();
          }
      }
    
      public override void Delete()
      {
          children?.Clear();
          children = null;
      }
    
      public override void Remove(Component component)
      {
          children.Remove(component);
          component = null;
      }
    }
  • File:叶子构件

    public class File : Component
    {
      public File(string name)
      {
          _name = name;
          IsDirectory = false;
          IsFile = true;
      }
    
      public override void Delete()
      {
          if (parent != null) parent.Remove(this);
      }
    
      public override void Display()
      {
          Console.WriteLine($"Name: {Name} Path: {Path}");
      }
    }
  • Client:客户端类

    Folder root = new Folder("root");
    
    Folder Photo = new Folder("Photo");
    root.Add(Photo);
    
    Main.File boyPng = new Main.File("boy.png");
    Main.File grilPng = new Main.File("gril.png");
    Folder JPG = new Folder("JPG");
    Main.File boyJPG = new Main.File("boy.jpg");
    Photo.Add(boyPng);
    Photo.Add(grilPng);
    Photo.Add(JPG);
    JPG.Add(boyJPG);
    
    Folder softWare = new("softWare");
    root.Add(softWare);
    Main.File qq = new Main.File("qq.exe");
    Main.File wx = new Main.File("wx.exe");
    softWare.Add(qq);
    softWare.Add(wx);
    
    root.Display();
    
    /*
    Name: root Path:
    Name: Photo Path: /Photo
    Name: boy.png Path: /Photo/boy.png
    Name: gril.png Path: /Photo/gril.png
    Name: JPG Path: /Photo/JPG
    Name: boy.jpg Path: /Photo/JPG/boy.jpg
    Name: softWare Path: /softWare
    Name: qq.exe Path: /softWare/qq.exe
    Name: wx.exe Path: /softWare/wx.exe
    */

四、优缺点

  • 优点:
    1. 定义层次:清楚地定义了复杂对象的分层次结构,表示对象的全部或者部分层次。
    2. 忽略层次:让客户端忽略层次之间的差异,方便对整个层次结构进行控制。
    3. 符合开闭原则:可以轻松增加新的组件类型,而且不需要修改现有代码。
  • 缺点:
    1. 限制类型复杂:在限制类型是,使用组合模式可能会变得复杂,比如有时候希望目录中只包含文本文件,但组合模式不能依赖类型系统施加约束,只能在运行时对类型进行检查。
    2. 使设计更加抽象:组合模式引入了更多抽象概念,使设计变得更加抽象。

五、使用场景

  1. 处理树形结构:组合模式非常适合处理具有层次结构的对象,例如游戏中的红点模式,界面管理都可以使用组合模式。
  2. 忽略差异:可以忽略组合对象和单个对象之间的差异,用一致的方式处理单个对象和组合对象。