• 二十三种设计模式[8]


    前言      

    组合模式,类结构模式的一种。在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 将对象组合成树状结构以表示 “部分-整体” 的层次结构,使得用户对单个对象和组合对象的使用具有一致性 ”。

           工作中我们经常会接触到一个对象中包含0个或多个其它对象,而其它对象依然包含0个或多个其它对象,这种结构我们称之为树状结构。组合模式就是通过递归去帮助我们去管理这类树状结构。

    结构

    Compsite_3

    需要角色如下:

    • Component(所有节点的抽象):所有对象(节点)的抽象或接口,用来定义所有节点的行为;
    • Leaf(叶节点):树状结构中的叶节点(没有子节点),继承抽象并实现行为;
    • Composite(根节点):树状结构中的根节点和子树的根节点,叶节点的容器,用来管理子节点;

    场景

           最经典的树状结构莫过于操作系统中的文件目录结构。我们都知道在一个文件夹中会包含0个或多个文件,而这些文件中又会包含0个或多个文件。如下。

    Compsite_

           在设计它的结构时往往会增加一个文件夹类,并根据文件夹内的文件类型维护相应的List去存储,以此类推。也就是说在文件夹类中,我们会根据不同的类型去创建不同的List,每当文件夹支持新的类型时我们都要去修改这个文件夹类,并不符合开闭原则。而且随着文件夹类支持的类型越多,这个类也将变得越来越复杂。

           使用组合模式使得我们在编码过程中不必过分关注各个文件的类型(只要是一个文件),并通过递归来简化文件夹类的设计。如下。

    Compsite_2

    示例

    Compsite_4

    public interface IFile
    {
        IFile Father { set; get; }
        bool IsFolder { get; }
        string ShowMyself();
        IFile GetChild(int index);
        void Add(IFile obj);
        void Remove(IFile obj);
    }
    
    public class Txt : IFile
    {
        public bool IsFolder => false;
        public string Name { set; get; } = string.Empty;
        public IFile Father { set; get; }
    
        public Txt(string name)
        {
            this.Name = name;
        }
    
        public string ShowMyself()
        {
            string spec = string.Empty;
            IFile father = this.Father;
            while (father != null)
            {
                spec += "  ";
                father = father.Father;
            }
            return $"{spec + this.Name}.txt";
        }
    
        public IFile GetChild(int index)
        {
            throw new NotImplementedException("Sorry,I have not Child");
        }
    
        public void Add(IFile obj)
        {
            throw new NotImplementedException("Sorry,I have not Child");
        }
    
        public void Remove(IFile obj)
        {
            throw new NotImplementedException("Sorry,I have not Child");
        }
    }
    
    public class Png : IFile
    {
        public bool IsFolder => false;
        public string Name { set; get; } = string.Empty;
        public IFile Father { set; get; }
    
        public Png(string name)
        {
            this.Name = name;
        }
    
        public string ShowMyself()
        {
            string spec = string.Empty;
            IFile father = this.Father;
            while (father != null)
            {
                spec += "  ";
                father = father.Father;
            }
            return $"{spec + this.Name}.png";
        }
    
        public IFile GetChild(int index)
        {
            throw new NotImplementedException("Sorry,I have not child");
        }
    
        public void Add(IFile obj)
        {
            throw new NotImplementedException("Sorry,I have not child");
        }
    
        public void Remove(IFile obj)
        {
            throw new NotImplementedException("Sorry,I have not child");
        }
    }
    
    public class Folder : IFile
    {
        public bool IsFolder => true;
        public string Name { set; get; } = string.Empty;
        public IFile Father { set; get; }
        private List<IFile> _childList = new List<IFile>();
    
        public Folder(string name)
        {
            this.Name = name;
        }
    
        public string ShowMyself()
        {
            string spec = string.Empty;
            IFile father = this.Father;
            while (father != null)
            {
                spec += "  ";
                father = father.Father;
            }
    
            string result = spec + this.Name;
            foreach (IFile child in _childList)
            {
                result += Environment.NewLine + child.ShowMyself();
            }
    
            return result;
        }
    
        public IFile GetChild(int index)
        {
            if(index >= this._childList.Count)
            {
                throw new Exception("越界");
            }
    
            return this._childList[index];
        }
    
        public void Add(IFile obj)
        {
            IFile father = this;
            while(father != null)
            {
                if(object.ReferenceEquals(obj, father))
                {
                    throw new Exception("循环引用");
                }
    
                father = father.Father;
            }
    
            if(this._childList.Exists(t=> object.ReferenceEquals(t, obj)))
            {
                throw new Exception("子节点已存在");
            }
    
            obj.Father = this;
            this._childList.Add(obj);
        }
    
        public void Remove(IFile obj)
        {
            if(obj.Father == null
                || !this._childList.Exists(t=> object.ReferenceEquals(t, obj)))
            {
                throw new Exception("未找到子节点");
            }
    
            obj.Father = null;
            this._childList.Remove(obj);
        }
    }
    
    static void Main(string[] args)
    {
        IFile folder = new Folder("我的文档");
        IFile txtFileA = new Txt("新建文本文档A");
        IFile pngFileA = new Png("QQ截图A");
        IFile folderA = new Folder("新建文件夹A");
    
        if (folder.IsFolder)
        {
            folder.Add(txtFileA);
            folder.Add(pngFileA);
            folder.Add(folderA);
        }
    
        IFile txtFileB = new Txt("新建文本文档B");
        IFile pngFileB = new Png("QQ截图B");
    
        if (folderA.IsFolder)
        {
            folderA.Add(txtFileB);
            folderA.Add(pngFileB);
        }
        
        Console.WriteLine(folder.ShowMyself());
        Console.ReadKey();
    }

    image

           在示例中,IFile接口定义了IFile类型的属性(在C#里,接口中可以定义属性)用来存储父节点,方便结构的向上操作。函数IsFolder用来标识当前对象是否是一个Composite。ShowMyself函数表示各个节点的基本操作,在Composite角色(Folder类)中一般递归调用子节点的ShowMyself函数。Add、Remove以及GetChild函数用来管理子节点。

           注意:管理子节点的操作函数是在组合模式中比较有争议的一个点。我们不难看出,对于叶节点(类Txt、Png)来说管理子节点的操作是没有意义的(因为它们没有子节点)。在IFile接口中声明这些操作能够保证节点的一致性以及结构的透明性,但会使调用者做一些无意义的操作(比如调用Txt类的Add函数)。而在Composite角色中定义这些操作虽然能够避免调用者的无意义操作,但会使节点的透明性和一致性降低。

           由于组合模式更加强调各个节点的一致性以及通明性,这里更加推荐在接口中定义那些管理子节点的函数。

           为了减少叶节点重复的实现这些对它无意义的子节点管理函数,可以使用适配器模式 (Adapter)对IFile接口做一个适配,为函数提供一个缺省的实现并使所有叶节点继承这个适配器。或者将IFile声明为一个抽象类并为函数提供缺省的实现。

    总结

           在组合模式中,通过定义节点的公共接口提高结构的一致性以及透明性,并通过递归来简化类的设计。在我们对结构进行扩展时,只需要增加接口的实现类而无需对现有代码进行改动,符合开闭原则。但在使用的过程中,我们很难实现对各个节点的约束,并且递归的使用使得我们需要花费更多的时间去理解它的层次关系。递归的使用也使得我们需要更加谨慎的处理结构的深度,以免造成内存溢出。

           以上,就是我对组合模式的理解,希望对你有所帮助。

           示例源码:https://gitee.com/wxingChen/DesignPatternsPractice

           系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html

           本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10078594.html)

  • 相关阅读:
    opencv_图像的色彩空間cvtColor(HSV、HSL、HSB )及相关色彩学
    ASCII码字符对照表
    机器学习_logistic回归和梯度下降
    mysql 安装异常:this application requires .NET Framework(尚未安装.NET Framework 4.5 原因是:指定)
    面向对象的基本原则----
    面试题:“你能不能谈谈,java GC是在什么时候,对什么东西,做了什么事情?”
    String 解析--创建对象存储分析
    <cache> does not allow attribute "maxBytesLocalDisk
    Your 30-day trial of MyEclipse has expired 解决方案
    svn: E175002: java.lang.RuntimeException: Could not generate DH keypair svn: E175002: OPTIONS request failed on '/svn/ERPHR/HR_633'
  • 原文地址:https://www.cnblogs.com/wxingchen/p/10078594.html
Copyright © 2020-2023  润新知