09、C# 设计模式-组合模式

组合模式(Composite Pattern)

组合模式属于结构型模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

角色:

1、 抽象构件(Component);

它是所有叶子构件和容器构件的共同父类,里面声明了叶子构件和容器构件的所有方法;

2、 叶子构件(Leaf);

在组合中表示叶子结点对象,叶子结点没有子结点,对于从父类中继承过来的容器构件的方法,由于它不能实现,可以抛出异常;

3、 容器构件(Composite);

定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(Attach)和删除(Detach)等。

示例:

 

命名空间CompositePattern包含文件系统FileSystem基类充当抽象构件类,文件File类充当叶子构件,文件夹Folder类充当容器构件,文件无效操作FileInvalidException 类进行异常处理。本案例尝试通过一个简单的文件系统来向大家阐述组合模式在调用方使用一致性方面的表现。

namespace CompositePattern
public abstract class FileSystem {

    protected string _name = null;

    protected const char SPLIT_CHAR_FILE = ' ';

    protected const char SPLIT_CHAR_DIR = '▼';

    public FileSystem(string name) {
        this._name = name;
    }

    public abstract FileSystem Attach(FileSystem component);

    public abstract FileSystem Detach(FileSystem component);

    public abstract void Print(int depth = 0);

}

抽象构件,文件系统FileSystem类。

public class File : FileSystem {

    public File(string name) : base(name) {

    }

    public override FileSystem Attach(FileSystem component) {
        throw new FileInvalidException(
            "You can not attach a component in a file!");
    }

    public override FileSystem Detach(FileSystem component) {
        throw new FileInvalidException(
            "You can not detach a component from a file!");
    }

    public override void Print(int depth = 0) {
        Console.WriteLine(new string(SPLIT_CHAR_FILE, depth) + _name);
    }

}

叶子构件,文件File类。

public class Folder : FileSystem {

    private List<FileSystem> _childrens = null;

    public Folder(string name) : base(name) {
        _childrens = new List<FileSystem>();
    }

    public override FileSystem Attach(FileSystem component) {
        _childrens.Add(component);
        return this;
    }

    public override FileSystem Detach(FileSystem component) {
        _childrens.Remove(component);
        return this;
    }

    public override void Print(int depth = 0) {
        Console.WriteLine(new string(SPLIT_CHAR_DIR, depth) + _name);
        foreach (var component in _childrens) {
            component.Print(depth + 1);
        }
    }

}

容器构件,文件夹Folder类。

public class FileInvalidException : Exception {

    public FileInvalidException(string message) : base(message) {

    }

    public FileInvalidException(string message, Exception innerException)
        : base(message, innerException) {

    }

}

无效的文件操作FileInvalidException类进行简单的异常处理。

public class Program {

    public static void Main(string[] args) {
        try {
            var root = new Folder("Root");

            var music = new Folder("My Music");
            music.Attach(new File("Heal the world.mp3"))
                    .Attach(new File("When You Say Nothing At All.mp3"))
                    .Attach(new File("Long Long Way to Go.mp3"))
                    .Attach(new File("Beautiful In White.mp3"));

            var video = new Folder("My Video");
            video.Attach(new File("The Shawshank Redemption.avi"))
                    .Attach(new File("Schindler's List.avi"))
                    .Attach(new File("Brave Heart.avi"));

            var png = new File("missing.png");
            root.Attach(png)
                .Detach(png)
                .Detach(png);

            root.Attach(new File("readme.txt"))
                .Attach(new File("index.html"))
                .Attach(new File("file.cs"))
                .Attach(music)
                .Attach(video)
                .Print();

            png.Attach(new File("error.json"));

        }
        catch (Exception ex) {
            Console.WriteLine(ex.Message);
        }

        Console.ReadKey();
    }

}

以上是调用方的代码,以下是这个案例的输出结果:

Root
 readme.txt
 index.html
 file.cs
▼My Music
  Heal the world.mp3
  When You Say Nothing At All.mp3
  Long Long Way to Go.mp3
  Beautiful In White.mp3
▼My Video
  The Shawshank Redemption.avi
  Schindler's List.avi
  Brave Heart.avi
You can not attach a component in a file!

优点:

1、 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易;
2、 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象;
3、 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构;
4、 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码;

缺点:

1、 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联;
2、 增加新构件时可能会产生一些问题,很难对容器中的构件类型进行限制;

使用场景:

1、 你想表示对象的部分-整体层次结构;
2、 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象;
3、 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们;