前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式 -- 组合模式

设计模式 -- 组合模式

作者头像
xy_ss
发布2023-11-22 09:25:45
1600
发布2023-11-22 09:25:45
举报
文章被收录于专栏:浮躁的喧嚣浮躁的喧嚣

场景

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

示例图

如图所示,文件夹中可以包含文件,还可以继续包含子文件夹,但是在文件中不能再包含子文件或者子文件夹。在此,我们可以称文件夹为容器(Container),而不同类型的各种文件是其成员,也称为叶子(Leaf)。如果我们现在要对某一个文件夹进行操作,如查找文件,那么需要对指定的文件夹进行遍历,如果存在子文件夹则打开其子文件夹继续遍历,如果是文件则判断之后返回查找结果

代码语言:javascript
复制
class ImageFile {
    var name :String
    init(name:String) {
        self.name = name
    }
    func killVirus()  {
        print("图片文件:\(self.name)进行杀毒")
    }
}

class TextFile {
    var name :String
    init(name:String) {
        self.name = name
    }
    func killVirus()  {
        print("文本文件:\(self.name)进行杀毒")
    }
}

class Folder {
    var name : String
    var folderArr = [Folder]()
    var imageArr = [ImageFile]()
    var textArr = [TextFile]()
    
    init(name:String)  {
        self.name = name
    }
    
    
    //增加新的Folder类型的成员
    func addFolder(f:Folder)  {
        self.folderArr.append(f)
    }
    //增加新的ImageFile类型的成员
    func addImageFile(image:ImageFile) {
        self.imageArr.append(image)
    }
    //增加新的TextFile类型的成员
    func addTextFile(text:TextFile) {
        self.textArr.append(text)
    }
    
    func killVirus() {
        print("\(self.name)进行杀毒")
        
        for fodler in folderArr {
            fodler.killVirus()
        }
        for image in imageArr {
            image.killVirus()
        }
        for text in textArr {
            text.killVirus()
        }
    }
}

客户端调用

代码语言:javascript
复制
let folder = Folder.init(name: "文件夹")
let folderA = Folder.init(name: "图片文件夹")
let folderB = Folder.init(name: "文本文件夹")
folder.addFolder(f: folderA)
folder.addFolder(f: folderB)

let imageA = ImageFile.init(name: "图片A")
let imageB = ImageFile.init(name: "图片B")
folderA.addImageFile(image: imageA)
folderA.addImageFile(image: imageB)

let textA = TextFile.init(name: "文本A")
let textB = TextFile.init(name: "文本B")
folderB.addTextFile(text: textA)
folderB.addTextFile(text: textB)

folder.killVirus()

log:
// 文件夹进行杀毒
// 图片文件夹进行杀毒
// 图片文件:图片A进行杀毒
// 图片文件:图片B进行杀毒
// 文本文件夹进行杀毒
// 文本文件:文本A进行杀毒
// 文本文件:文本B进行杀毒

问题来了

  • 文件夹类Folder的实现都非常复杂,需要定义多个集合存储不同类型的成员,而且需要针对不同的成员提供增加、删除和获取等管理和访问成员的方法,存在大量的冗余代码,系统维护较为困难
  • 由于系统没有提供抽象层,客户端代码必须有区别地对待充当容器的文件夹Folder和充当叶子的ImageFile和TextFile,无法统一对它们进行处理
  • 系统的灵活性和可扩展性差,如果需要增加新的类型的叶子和容器都需要对原有代码进行修改,例如如果需要在系统中增加一种新类型的视频文件VideoFile,则必须修改Folder类的源代码,否则无法在文件夹中添加视频文件

问题改进 运用组合模式处理树形结构的问题,将容器和叶子进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地处理容器和叶子

表述 (结构型模式)

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

组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理

组合模式类图

组合模式类图

  • Component(抽象构件):可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等
  • Leaf(叶子构件):表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理
  • Composite(容器构件):表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法

优点

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
  • 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”

缺点

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

使用场景

  • 在一个使用面向对象语言开发的系统中需要处理一个树形结构
  • 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型

示例

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

代码语言:javascript
复制
//一般将抽象构件类设计为接口或抽象类,将所有子类共有方法的声明和实现放在抽象构件类中
class File {
    var name : String;
    init(name:String) {
        self.name = name
    }
    
    func add(f:File) {
        
    }
    func remove(f:File) {
        
    }
    func getChild(i:Int) -> File {
        return File.init(name: "")
    }
    func killVirus() {
        
    }
}

//图像文件类:叶子构件
class ImageFile : File {    
    override init(name: String) {
        super.name = name
    }
    
    override func add(f: File) {
        print("不支持该方法")
    }
    override func remove(f: File) {
        print("不支持该方法")
    }
    override func getChild(i: Int) -> File {
        print("不支持该方法")
        return File.init(name: "")
    }
    override func killVirus() {
        print("图片文件:\(self.name)进行杀毒")
    }
}

//文本文件类:叶子构件
class TextFile : File {
    override init(name: String) {
        super.name = name
    }
    
    override func add(f: File) {
        print("不支持该方法")
    }
    override func remove(f: File) {
        print("不支持该方法")
    }
    override func getChild(i: Int) -> File {
        print("不支持该方法")
        return File.init(name: "")
    }
    override func killVirus() {
        print("文本文件:\(self.name)进行杀毒")
    }
}

//文件夹类:容器构件
class Folder : File {
    var fileArray = [File]()
    override init(name: String) {
        super.init(name: name)
    }
    
    override func add(f: File) {
        fileArray.append(f)
    }
    override func remove(f: File) {
        for i in 0..<fileArray.count {
            if fileArray[i].name == f.name {
                fileArray.remove(at: i)
                break
            }
        }
    }
    override func getChild(i: Int) -> File {
        for i in 0..<fileArray.count {
            return fileArray[i]
        }
    }
    
    override func killVirus() {
        print("\(self.name)进行杀毒")
        for obj in fileArray {
            obj.killVirus()
        }
    }
}

客户端:

代码语言:javascript
复制
let folder = Folder.init(name: "文件夹")
let folderA = Folder.init(name: "图片文件夹")
let folderB = Folder.init(name: "文本文件夹")
folder.addFile(f: folderA)
folder.addFile(f: folderB)

let folder = Folder.init(name: "文件夹")
let imageFolder = Folder.init(name: "图片文件夹")
let textFolder = Folder.init(name: "文本文件夹")
folder.addFile(f: imageFolder)
folder.addFile(f: textFolder)

let imageFileA = ImageFile.init(name: "图片A")
let imageFileB = ImageFile.init(name: "图片B")
imageFolder.addFile(f: imageFileA)
imageFolder.addFile(f: imageFileB)

let textFileA = TextFile.init(name: "文本A")
let textFileB = TextFile.init(name: "文本B")
textFolder.addFile(f: textFileA)
textFolder.addFile(f: textFileB)

folder.killVirus()
log:
//文件夹进行杀毒
//图片文件夹进行杀毒
//图片文件:图片A进行杀毒
//图片文件:图片B进行杀毒
//文本文件夹进行杀毒
//文本文件:文本A进行杀毒
//文本文件:文本B进行杀毒

需求V2:在系统中增加一种新类型的视频文件VideoFile

只需要新建VideoFile继承自File即可

代码语言:javascript
复制
class VideoFile : File {
    override init(name: String) {
        super.init(name: name)
    }

    override func addFile(f: File) {
        print("不支持该方法")
    }
    override func removeFile(f: File) {
        print("不支持该方法")
    }
    override func getChild(i: Int) -> File {
        print("不支持该方法")
        return File.init(name: "")
    }
    override func killVirus() {
        print("视频文件:\(self.name)进行杀毒")
    }
}

客户端

代码语言:javascript
复制
let videoFolder = Folder.init(name: "视频文件夹")
folder.addFile(f: videoFolder)

let videoFileA = VideoFile.init(name: "视频A")
let videoFileB = VideoFile.init(name: "视频B")
videoFolder.addFile(f: videoFileA)
videoFolder.addFile(f: videoFileB)

folder.killVirus()

log
//视频文件夹进行杀毒
//视频文件:视频A进行杀毒
//视频文件:视频B进行杀毒
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景
  • 表述 (结构型模式)
  • 组合模式类图
  • 优点
  • 缺点
  • 使用场景
  • 示例
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档