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