设计模式的征途—9.组合(Composite)模式

树形结构在软件中随处可见,比如操作系统中的目录结构,公司组织结构等等,如何运用面向对象的方式来处理这种树形结构是组合模式需要解决的问题。组合模式通过一种巧妙的设计方案来使得用户可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致地处理树形结构中的叶子节点(不包含子节点的节点)和容器节点(包含子节点的节点),本次我们就将学习一下用来处理树形结构的组合模式。

组合模式(Composite)

学习难度:★★★☆☆

使用频率:★★★★☆

一、杀毒软件的框架设计

1.1 需求介绍

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

  首先,我们来了解一下Windows操作系统中的目录结构:

1.2 初始设计

  M公司程序猿们通过分析,决定使用面向对象的方式来实现对文件和文件夹的操作,定义了图像文件类ImageFile、文本文件类TextFile和文件夹类Folder,代码如下:

  (1)文件类:

    public class ImageFile
    {
        private string name;

        public ImageFile(string name)
        {
            this.name = name;
        }

        public void KillVirus()
        {
            // 此处模拟杀毒操作
            Console.WriteLine("---- 对图像文件‘{0}’进行杀毒", name);
        }
    }

    public class TextFile
    {
        private string name;

        public TextFile(string name)
        {
            this.name = name;
        }

        public void KillVirus()
        {
            // 此处模拟杀毒操作
            Console.WriteLine("---- 对文本文件‘{0}’进行杀毒", name);
        }
    }

  (2)文件夹类:

    public class Folder
    {
        private string name;

        private IList<Folder> folderList = new List<Folder>();
        private IList<ImageFile> imageList = new List<ImageFile>();
        private IList<TextFile> textList = new List<TextFile>();

        public Folder(string name)
        {
            this.name = name;
        }

        public void AddFolder(Folder f)
        {
            folderList.Add(f);
        }

        public void AddImageFile(ImageFile image)
        {
            imageList.Add(image);
        }

        public void AddTextFile(TextFile text)
        {
            textList.Add(text);
        }

        // 需要提供3个不同的方法 RemoveFolder, RemoveImageFile, RemoveTextFile来删除成员,代码省略

        // 需要提供3个不同的方法 GetChildFolder(int i), GetChildImageFile(int i), GetChildTextFile(int i)来获取成员,代码省略

        public void KillVirus()
        {
            Console.WriteLine("**** 对文件夹'{0}'进行杀毒", name);

            foreach (var item in folderList)
            {
                item.KillVirus();
            }

            foreach (var item in imageList)
            {
                item.KillVirus();
            }

            foreach (var item in textList)
            {
                item.KillVirus();
            }
        }
    }

  (3)客户端调用:

     public class Program
     {
        public static void Main()
        {
            Folder folder1 = new Folder("EDC的资料");
            Folder folder2 = new Folder("图像文件");
            Folder folder3 = new Folder("文本文件");

            ImageFile image1 = new ImageFile("小龙女.jpg");
            ImageFile image2 = new ImageFile("张无忌.gif");

            TextFile text1 = new TextFile("九阴真经.txt");
            TextFile text2 = new TextFile("葵花宝典.doc");

            folder2.AddImageFile(image1);
            folder2.AddImageFile(image2);

            folder3.AddTextFile(text1);
            folder3.AddTextFile(text2);

            folder1.AddFolder(folder2);
            folder1.AddFolder(folder3);

            folder1.KillVirus();
        }
    }

  执行结果如下图所示:

  虽然程序员们“成功”实现了这个软件的框架设计,但通过分析,发现存在以下问题:

  (1)文件类Folder的设计和实现很复杂,需要定义多个集合存储不同类型的成员,存在大量的冗余代码,系统维护较为困难。

  (2)系统没有提供抽象层,客户端代码必须有区别地对待充当容器的文件夹Folder和充当叶子的ImageFile和TextFile,无法统一对它们进行处理。

  (3)系统的灵活性和可扩展性差,如果需要增加新的类型的叶子和容器都需要对原有代码进行修改。

二、组合模式简介

2.1 模式概述

组合(Composite)模式:组合多个对象形成树形结构以表示具有“整体-部分”关系的层次结构。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性,组合模式又可以称为“部分-整体”(Part-Whole)模式,它是一种对象结构型模式。  

2.2 结构图

  在组合模式中引入了抽象构件类Component,它是所有容器类和叶子类的公共父类,客户端针对Component进行编程。组合模式结构如下图所示:

  组合模式包含以下几个角色:

  (1)Component(抽象构件):它是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,例如增加子构件、删除子构件、获取子构件等。

  (2)Leaf(叶子构件):它在组合模式中表示叶子结点对象,叶子结点没有子节点,它实现了在抽象构件中定义的行为。

  (3)Composite(容器构件):它在组合模式中表示容器节点对象,容器节点包含子节点,其子节点可以使叶子结点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为。

三、重构杀毒软件框架设计

3.1 重构后的设计结构

  其中,AbstractFile充当抽象构件类,Folder充当容器类,ImageFile、TextFile以及VideoFile充当叶子构件类。

3.2 重构后的代码实现

  (1)抽象构件:AbstractFile

    /// <summary>
    ///  抽象文件类:抽象构件
    /// </summary>
    public abstract class AbstractFile
    {
        public abstract void Add(AbstractFile file);
        public abstract void Remove(AbstractFile file);
        public abstract AbstractFile GetChild(int index);
        public abstract void KillVirus();
    }

  (2)叶子构件:ImageFile、VideoFile、TextFile类

    /// <summary>
    /// 叶子构件:图像文件、文本文件 和 视频文件
    /// </summary>
    public 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 index)
        {
            Console.WriteLine("对不起,系统不支持该方法!");
            return null;
        }

        public override void KillVirus()
        {
            // 此处模拟杀毒操作
            Console.WriteLine("**** 对图像文件‘{0}’进行杀毒", name);
        }
    }

    public class TextFile : AbstractFile
    {
        private string name;

        public TextFile(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 index)
        {
            Console.WriteLine("对不起,系统不支持该方法!");
            return null;
        }

        public override void KillVirus()
        {
            // 此处模拟杀毒操作
            Console.WriteLine("**** 对文本文件‘{0}’进行杀毒", name);
        }
    }

    public class VideoFile : AbstractFile
    {
        private string name;

        public VideoFile(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 index)
        {
            Console.WriteLine("对不起,系统不支持该方法!");
            return null;
        }

        public override void KillVirus()
        {
            // 此处模拟杀毒操作
            Console.WriteLine("**** 对视频文件‘{0}’进行杀毒", name);
        }
    }

  (3)容器构件:Folder

    /// <summary>
    /// 文件夹类:容器构件
    /// </summary>
    public class Folder : AbstractFile
    {
        private IList<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 index)
        {
            return fileList[index];
        }

        public override void KillVirus()
        {
            // 此处模拟杀毒操作
            Console.WriteLine("---- 对文件夹‘{0}’进行杀毒", name);

            foreach (var item in fileList)
            {
                item.KillVirus();
            }
        }
    }

  (4)客户端调用

    public class Program
    {
        public static void Main()
        {
            AbstractFile folder1 = new Folder("EDC的资料");
            AbstractFile folder2 = new Folder("图像文件");
            AbstractFile folder3 = new Folder("文本文件");
            AbstractFile folder4 = new Folder("视频文件");

            AbstractFile image1 = new ImageFile("小龙女.jpg");
            AbstractFile image2 = new ImageFile("张无忌.gif");

            AbstractFile text1 = new TextFile("九阴真经.txt");
            AbstractFile text2 = new TextFile("葵花宝典.doc");

            AbstractFile video1 = new VideoFile("笑傲江湖.rmvb");
            AbstractFile video2 = new VideoFile("天龙八部.mp4");

            folder2.Add(image1);
            folder2.Add(image2);

            folder3.Add(text1);
            folder3.Add(text2);

            folder4.Add(video1);
            folder4.Add(video2);

            folder1.Add(folder2);
            folder1.Add(folder3);
            folder1.Add(folder4);

            folder1.KillVirus();
        }
    }

  执行结果如下图所示:

  如果只需更换操作节点,例如只需要针对文件夹“文本文件”进行杀毒,只需要修改客户端代码如下:

    //folder1.KillVirus();
    folder3.KillVirus();

  执行结果如下图所示:

  在具体实现时,可以创建图形界面让用户自己选择所需操作的根节点,无需修改源代码,符合开闭原则,客户端无须关心节点的层次结构,可以对所选节点进行统一处理,提高系统的灵活性。

四、组合模式小结

4.1 主要优点

  (1)可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使客户忽略了层次的差异,方便对整个层次结构进行控制。

  (2)增加新的容器构件和叶子构件都十分方便,无需对现有类库代码进行任何修改,符合开闭原则

  (3)为树形结构的面向对象实现提供了灵活地解决方案,可以形成复杂的树形结构,但对树形结构的控制却很简单

4.2 主要缺点

  增加新构件时很难对容器中的构建类型进行限制。

4.3 适用场景

  (1)在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待他们。

  (2)在一个使用面向对象语言开发的系统中需要处理一个树形结构。

参考资料

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏点滴积累

PhiloGL学习(4)——三维对象、加载皮肤

前言 上一篇文章中介绍了如何响应鼠标和键盘事件,本文介绍如何加载三维对象并实现给三维对象添加一个漂亮的皮肤。 一、 原理分析 我对三维的理解为:所谓三维对象无非...

3476
来自专栏逸鹏说道

Python3 与 C# 面向对象之~异常相关

又到了开新课的时候了,小明同学这次提前预习了知识,乘着老师还没来就在黑板上写下了这段Code:

1183
来自专栏喔家ArchiSelf

一行Python代码

自从08年接触Python,就有爱不释手的感觉,逐渐地,有些不忍地疏远了Perl 和Shell编程,因为python 的优雅么? 不全是,主要是可以高效开发吧。

1964
来自专栏计算机视觉与深度学习基础

模拟猜单词游戏

模拟实现猜单词游戏,纯模拟,不涉及图形界面,注释很详细,虽然本人代码写得丑,但是希望可以给大家提供帮助 #include<algorithm> #include...

20410
来自专栏tkokof 的技术,小趣及杂念

'24点'编码小感

之前看到了一道四则运算相关的程序题,遂而想到了24点游戏,觉得有趣,就想自己随手编写了一个,起初觉得应该比较简单,但实际的路途却并不平坦~

1032
来自专栏AI派

如何使用Python伪造一点也不假的假数据呢

工作中,有时候我们需要伪造一些假数据,如何使用 Python 伪造这些看起来一点也不假的假数据呢?

1663
来自专栏熊二哥

.NET工作准备--01前言

01应聘须知(已过时) -1.了解软件开发大环境。 -2.准备简历:不宜超过一页,永远准备中文,模板。 -3.渠道:3大网站,中华英才,前程无忧(51job最...

2128
来自专栏NetCore

[原创]Fluent NHibernate之旅(四)-- 关系(下)

最近一直忙着准备去旅行的东东,所以进度慢下来了,明天就要出发了,嘿嘿,在出发前,把多对多给写完吧。如果你第一次看这个系列,可以先看看先前几篇,了解下。 一、开篇...

21310
来自专栏积累沉淀

Java设计模式(十三)----策略模式

策略模式 一、概述 二、策略模式的结构 三、具体案例 四、认识策略模式 一、概述 1.定义 策略模式属于对象行为型模式,主要针对一组算法...

2226
来自专栏程序猿DD

并行化:你的高并发大杀器

想必热爱游戏的同学小时候,都幻想过要是自己要是能像鸣人那样会多重影分身之术,就能一边打游戏一边上课了,可惜漫画就是漫画,现实中并没有这个技术,你要么只有老老实实...

2033

扫码关注云+社区

领取腾讯云代金券