设计模式 -- 组合模式

2023-11-22 09:25:45 浏览数 (1)

场景

某公司准备开发一个杀毒软件,该软件既可以对某个文件夹(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进行杀毒

0 人点赞