设计模式的艺术之道--组合模式

来源:互联网 发布:派派交友软件 编辑:程序博客网 时间:2024/05/18 23:13

设计模式的艺术之道–组合模式

声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐

本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).

本系列全部源码均在文末地址给出。


本系列开始讲解结构型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。
不同的结构型模式从不同的角度组合类或对象,它们在尽可能满足各种面向对象设计原则的同时为类或对象的组合提供一系列巧妙的解决方案。

  • 类结构型模式
    关心类的组合,由多个类组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系
  • 对象结构型模式
    关心类与对象的组合,通过关联关系,在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法
    7种常见的结构型模式
    这里写图片描述

组合模式

组合模式针对的是树形结构的层级问题。例如操作系统中的目录结构、应用软件中的菜单、办公系统中的公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的问题,组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致性地处理树形结构中的叶子节点(不包含子节点的节点)和容器节点(包含子节点的节点)
这里写图片描述
在树形目录结构中,包含文件和文件夹两类不同的元素

  • 在文件夹中可以包含文件,还可以继续包含子文件夹
  • 在文件中不能再包含子文件或者子文件夹
  • 文件夹——容器(Container)
  • 文件——叶子(Leaf)
    怎样一致地处理容器和叶子节点

1.1定义

  • 组合模式 (Composite Pattern):组合多个对象形成树形结构以表示具有部分-整体关系的层次结构。组合模式让客户端可以统一对待单个对象和组合对象。
  • 将对象组织到树形结构中,可以用来描述整体与部分的关系
  • 公司人员等级划分(老板 经理 主管 组长 普通员工) 行政单位划分(省 市 镇 村)

1.2情景实例

问题描述
- 杀毒软件设计
菜鸟软件公司欲开发一个杀毒软件,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。现需要提供该杀毒软件的整体框架设计方案。

初步思路
分析一下操作系统中的文件目录结构,是典型的树形结构。
我们可以称文件夹为容器(Container),而不同类型的各种文件是其成员,也称为叶子(Leaf),一个文件夹也可以作为另一个更大的文件夹的成员。如果我们现在要对某一个文件夹进行操作,如查找文件,那么需要对指定的文件夹进行遍历,如果存在子文件夹则打开其子文件夹继续遍历,如果是文件则判断之后返回查找结果。(重点看一下容易和叶子的概念)
开发人员通过分析,决定使用面向对象的方式来实现对文件和文件夹的操作,定义了如下图像文件类ImageFile、文本文件类TextFile和文件夹类Folder:如果是文本文件或者图像文件直接杀毒,文件夹文件需要递归调用杀毒。
初步关键代码

namespace Preliminary{    class Program    {        static void Main(string[] args)        {            Folder folder1, folder2, folder3;            folder1 = new Folder("Sunny的资料");            folder2 = new Folder("图像文件");            folder3 = new Folder("文本文件");            ImageFile image1, image2;            image1 = new ImageFile("小龙女.jpg");            image2 = new ImageFile("张无忌.gif");            TextFile text1, text2;            text1 = new TextFile("九阴真经.txt");            text2 = new TextFile("葵花宝典.doc");            folder2.addImageFile(image1);            folder2.addImageFile(image2);            folder3.addTextFile(text1);            folder3.addTextFile(text2);            folder1.addFolder(folder2);            folder1.addFolder(folder3);            folder1.killVirus();            Console.ReadLine();        }        //图像文件类          class ImageFile        {            private String name;            public ImageFile(String name)            {                this.name = name;            }            public void killVirus()            {                //简化代码,模拟杀毒                  Console.WriteLine("----对图像文件'" + name + "'进行杀毒");            }        }        //文本文件类          class TextFile        {            private String name;            public TextFile(String name)            {                this.name = name;            }            public void killVirus()            {                //简化代码,模拟杀毒                  Console.WriteLine("----对文本文件'" + name + "'进行杀毒");            }        }        //文件夹类          class Folder        {            private String name;            //定义集合folderList,用于存储Folder类型的成员              private List<Folder> folderList = new List<Folder>();            //定义集合imageList,用于存储ImageFile类型的成员              private List<ImageFile> imageList = new List<ImageFile>();            //定义集合textList,用于存储TextFile类型的成员              private List<TextFile> textList = new List<TextFile>();            public Folder(String name)            {                this.name = name;            }            //增加新的Folder类型的成员              public void addFolder(Folder f)            {                folderList.Add(f);            }            //增加新的ImageFile类型的成员              public void addImageFile(ImageFile image)            {                imageList.Add(image);            }            //增加新的TextFile类型的成员              public void addTextFile(TextFile text)            {                textList.Add(text);            }            //需提供三个不同的方法removeFolder()、removeImageFile()和removeTextFile()来删除成员,代码省略              //需提供三个不同的方法getChildFolder(int i)、getChildImageFile(int i)和getChildTextFile(int i)来获取成员,代码省略              public void killVirus()            {                Console.WriteLine("****对文件夹'" + name + "'进行杀毒");  //模拟杀毒                  //如果是Folder类型的成员,递归调用Folder的killVirus()方法                  foreach (Object obj in folderList)                {                    ((Folder)obj).killVirus();                }                //如果是ImageFile类型的成员,调用ImageFile的killVirus()方法                  foreach (Object obj in imageList)                {                    ((ImageFile)obj).killVirus();                }                //如果是TextFile类型的成员,调用TextFile的killVirus()方法                  foreach (Object obj in textList)                {                    ((TextFile)obj).killVirus();                }            }        }    }}

现有缺点(未来变化)
(1)文件夹类Folder的设计和实现都非常复杂,存在大量的冗余代码,系统维护较为困难
(2)没有提供抽象层,客户端代码无法统一处理充当容器的文件夹Folder和充当叶子的ImageFile和TextFile
(3)系统的灵活性和可扩展性差,如果需要增加新的类型的叶子和容器都需要对原有代码进行修改
如何改进
让叶子对象和容器对象的使用具有一致性,使得客户端可以统一的处理这些对象,引入组合模式。
抽象提升叶子节点和容器节点为组件节点,使客户端可以统一处理。容器节点中同时又可以包含容器节点或者叶子节点,然后分别在子类中各自完成自身的实现。
改进UML类图
这里写图片描述
实例关键代码

namespace Composite{    abstract class AbstractFile    {        public abstract void Add(AbstractFile file);        public abstract void Remove(AbstractFile file);        public abstract AbstractFile GetChild(int i);        public abstract void KillVirus();    }     class Folder : AbstractFile     {        //定义集合fileList,用于存储AbstractFile类型的成员        private List<AbstractFile> fileList = new List<AbstractFile>();        private string name;        public Folder(string name)         {            this.name = name;        }        public override void Add(AbstractFile file)        {           fileList.Add(file);          }        public override void Remove(AbstractFile file)        {            fileList.Remove(file);        }        public override AbstractFile GetChild(int i)        {            return (AbstractFile)fileList[i];        }        public override void KillVirus()         {            Console.WriteLine("****对文件夹'{0}'进行杀毒",name);  //模拟杀毒            //递归调用成员构件的killVirus()方法            foreach(Object obj in fileList)             {                ((AbstractFile)obj).KillVirus();            }        }    }    //其他叶子节点略    class ImageFile : AbstractFile     {        private string name;        public ImageFile(string name)         {            this.name = name;        }        public override void Add(AbstractFile file)         {            Console.WriteLine("对不起,不支持该方法!");        }        public override void Remove(AbstractFile file)         {            Console.WriteLine("对不起,不支持该方法!");        }        public override AbstractFile GetChild(int i)         {            Console.WriteLine("对不起,不支持该方法!");            return null;        }        public override void KillVirus()         {            //模拟杀毒            Console.WriteLine("----对图像文件'{0}'进行杀毒",name);        }    }    class Program    {        static void Main(string[] args)        {            //针对抽象构件编程            AbstractFile file1, file2, file3, file4, file5, folder1, folder2, folder3, folder4;            folder1 = new Folder("我的资料");            folder2 = new Folder("图像文件");            folder3 = new Folder("文本文件");            folder4 = new Folder("视频文件");            file1 = new ImageFile("小龙女.jpg ");            file2 = new ImageFile("张无忌.gif ");            file3 = new TextFile("九阴真经.txt ");            file4 = new TextFile("葵花宝典.doc ");            file5 = new VideoFile("神雕侠侣.rmvb");            folder2.Add(file1);            folder2.Add(file2);            folder3.Add(file3);            folder3.Add(file4);            folder4.Add(file5);            folder1.Add(folder2);            folder1.Add(folder3);            folder1.Add(folder4);            //从“我的资料”节点开始进行杀毒操作            folder1.KillVirus();            Console.Read();        }    }}

1.3模式分析

动机和意图

  • 对于多个层次的等级结构,如何进行层级的处理?
  • 如何一致地对待容器对象和叶子对象?

一般结构

  • 组合模式包含3个角色:
  • Component(抽象构件) 抽象提升归类叶子节点和容器节点
  • Leaf(叶子构件) 节点中不可在划分的单位处理
  • Composite(容器构件) 可以容纳其他节点类型的节点(包括自身的类型)

    组合模式UML类图
    这里写图片描述

改进的优点

  • 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,让客户端忽略了层次的差异,方便对整个层次结构进行控制
  • 可以一致地使用一个组合结构或其中单个对象,不必关心具体处理,简化了客户端代码
  • 增加新的容器构件和叶子构件都很方便,符合开闭原则

现存的缺点

  • 在增加新构件时很难对容器中的构件类型进行限制
  • 有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。

优化空间
由于在AbstractFile中声明了大量用于管理和访问成员构件的方法,例如add()、remove()等方法,我们不得不在新增的文件类中实现这些方法,提供对应的错误提示和异常处理。叶子节点并不需要这些方法,或者需要进行安全校验。

解决方案一:将叶子构件的add()、remove()等方法的实现代码移至AbstractFile类中,由AbstractFile提供统一的默认实现,代码如下所示:

    abstract class AbstractFile    {        public virtual void Add(AbstractFile file)        {            Console.WriteLine("对不起,不支持该方法!");        }        public virtual void Remove(AbstractFile file)        {            Console.WriteLine("对不起,不支持该方法!");        }        public virtual AbstractFile GetChild(int i)        {            Console.WriteLine("对不起,不支持该方法!");            return null;        }        public abstract void KillVirus();    }

如果不希望出现任何错误提示,我们可以在客户端定义文件对象时不使用抽象层,而直接使用具体叶子构件本身,这样就产生了一种不透明的使用方式,即在客户端不能全部针对抽象构件类编程,需要使用具体叶子构件类型来定义叶子对象。使用Add方法之前可以进行判断是否是叶子节点。

解决方案二:除此之外,还有一种解决方法是在抽象构件AbstractFile中不声明任何用于访问和管理成员构件的方法,代码如下所示:

abstract class AbstractFile {      public abstract void KillVirus();  }  static void Main(string[] args){    Folder folder1 = new Folder("我的资料");    Folder folder2 = new Folder("图像文件");    Folder folder3 = new Folder("文本文件");    Folder folder4 = new Folder("视频文件");}

由于在AbstractFile中没有声明add()、remove()等访问和管理成员的方法,其叶子构件子类无须提供实现;而且无论客户端如何定义叶子构件对象都无法调用到这些方法,不需要做任何错误和异常处理,容器构件再根据需要增加访问和管理成员的方法,但这时候也存在一个问题:客户端不得不使用容器类本身来声明容器构件对象,否则无法访问其中新增的add()、remove()等方法

适用场景
(1) 在具有整体和部分的层次结构中,希望忽略整体与部分的差异,客户端可以一致地对待它们。
(2)在一个使用面向对象语言开发的系统中需要处理一个树形结构问题。
(3)在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型

举例:公司的人员组织结构图 快递物流存储仓库

实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1pLmP1I7 密码: ern2

原创粉丝点击