【愚公系列】2023年10月 二十三种设计模式(八)-组合模式(Composite Pattern)
🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,阿里云专家博主,腾讯云优秀博主,掘金优秀博主,51CTO博客专家等。
🏆《近期荣誉》:2022年CSDN博客之星TOP2,2022年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏
🚀前言
设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。
毫无疑问,设计模式对个人、团队和整个系统都带来了显著的益处。它们将代码开发提升到工程化水平,为软件工程构建提供了坚实的基础,就如同大厦的一块块精巧的砖石一样。在项目中明智地应用设计模式可以完美地解决各种复杂问题。每种设计模式都有相应的原理和最佳实践,它们描述了我们日常开发中不断遇到的问题,以及这些问题的核心解决方法。正是因为这种实用性和通用性,设计模式才能在软件开发中广泛地得以应用。设计模式是构建稳健、可扩展和可维护软件的关键工具,为开发者们提供了解决问题的智慧和指导。
🚀一、组合模式(Composite Pattern)
组合模式是一种结构型设计模式,它允许将对象组织成树形结构,以表示"部分-整体"的层次结构。这种模式模糊了单个对象和组合对象之间的界限,使得客户端可以一致地对待它们。换句话说,组合模式允许客户端像处理单个对象一样处理复杂的层次结构,从而降低了客户端与内部结构的耦合度。
组合模式通常由以下几个要素组成:
组件(Component):定义了单个对象和组合对象的公共接口,使得它们可以被一致地处理。
叶子节点(Leaf):表示树结构中的单个对象,它实现了组件接口。
复合节点(Composite):表示树结构中的组合对象,它也实现了组件接口,但通常包含了一组子节点,可以递归地构建更复杂的结构。
客户端(Client):使用组合模式的应用程序,通过组件接口与树结构交互,而不需要知道对象的具体类型。
组合模式适用于以下情况:
- 当你需要表示部分-整体的层次结构,例如,文件系统中的目录和文件,图形界面中的控件和容器等。
- 当你希望客户端代码能够统一地处理单个对象和组合对象,而无需关心它们的具体类型。
- 当你需要对整个层次结构进行递归操作,而不必担心对象类型的差异。
通过使用组合模式,你可以更好地组织和管理复杂的层次结构,提高了系统的可扩展性和灵活性。这种模式的一个典型应用是在图形界面库中,其中控件和容器可以被组合成复杂的用户界面。这样,用户可以以一致的方式处理单个控件和整个用户界面。
🚀二、使用步骤
🔎1.角色
🦋1.1 抽象构件(Component )
在组合模式(Composite Pattern)中,抽象构件(Component)是一个关键的概念,它具有重要的作用。抽象构件定义了组合模式中所有对象的公共接口,不管是叶子节点(Leaf)还是复合节点(Composite)。以下是抽象构件的概念和作用:
概念:
- 抽象构件是一个抽象类或接口,它声明了用于叶子节点和复合节点的共同操作。
- 抽象构件通常包括对于添加子节点、删除子节点、获取子节点、以及执行操作等方法的声明。
- 抽象构件定义了组合模式中的统一接口,使得叶子节点和复合节点都能够被一致地对待。
作用:
- 定义一致的接口:抽象构件确保所有的叶子节点和复合节点都实现了相同的接口,这意味着客户端代码可以以一致的方式与它们交互。
- 封装通用行为:抽象构件可以包含一些通用的行为,例如添加和删除子节点的操作,这些行为对于整个树形结构都是通用的,因此可以在抽象构件中进行实现。
- 支持递归操作:通过抽象构件定义的接口,客户端可以递归地遍历整个树形结构,执行操作,而无需关心是叶子节点还是复合节点。
抽象构件在组合模式中起到了关键作用,它提供了一个统一的接口,使得叶子节点和复合节点可以被一致地管理和操作。通过抽象构件,组合模式实现了"部分-整体"的层次结构,并允许客户端代码以统一的方式处理整个结构,从而降低了客户端与内部结构的耦合度,提高了系统的灵活性和可扩展性。
🦋1.2 叶子构件(Leaf)
在组合模式(Composite Pattern)中,叶子构件(Leaf)是其中一个重要的概念,具有特定的作用。以下是叶子构件的概念和作用:
概念:
- 叶子构件是组合模式中的一个基本元素,它表示树形结构中的最终、不可再分的叶子节点。
- 叶子构件实现了抽象构件(Component)定义的接口,但通常不包含子节点,因为它无法再进一步组合。
- 叶子构件通常表示系统中的最小单元或原子元素,它不会有子节点。
作用:
- 表示基本元素:叶子构件用于表示组合模式中的基本元素或最终节点。例如,在文件系统中,文件可以被视为叶子构件,因为它们不再包含其他文件或目录。
- 实现统一接口:叶子构件必须实现抽象构件定义的接口,这使得客户端可以以一致的方式处理叶子节点和复合节点。这是组合模式的关键之一,它允许客户端无需关心对象的具体类型。
- 限制操作:由于叶子构件没有子节点,因此它通常会在添加子节点、删除子节点等操作上抛出异常或不做任何操作,以防止客户端试图对其进行组合操作。
叶子构件在组合模式中表示了最终的、不可再分的基本元素,它们实现了抽象构件定义的接口,以便与复合节点一致地交互。叶子构件的作用是将系统中的最小单元进行抽象表示,同时确保客户端可以统一处理整个树形结构,从而实现"部分-整体"的层次结构。
🦋1.3 容器构件(Composite)
在组合模式(Composite Pattern)中,容器构件(Composite)是一个关键概念,具有特定的作用。以下是容器构件的概念和作用:
概念:
- 容器构件是组合模式中的一个重要元素,它表示树形结构中的复合节点,可以包含其他叶子构件和容器构件作为其子节点。
- 容器构件实现了抽象构件(Component)定义的接口,同时也包含了子节点的集合,用于管理和组织子节点。
- 容器构件的结构使得它可以递归地构建更复杂的树形结构,允许嵌套包含叶子节点和其他容器构件。
作用:
- 组合对象:容器构件充当组合模式中的组合对象,它们可以包含一组子节点,这些子节点可以是叶子构件或其他容器构件。这种组合允许构建具有多层次结构的对象。
- 递归操作:容器构件可以递归地遍历其子节点,执行操作,然后递归调用子节点的操作。这使得可以对整个树形结构进行深度操作,而不仅仅是顶层节点。
- 简化客户端代码:容器构件和叶子构件都实现了相同的抽象构件接口,这使得客户端代码可以以一致的方式处理复合节点和叶子节点。客户端无需知道对象的具体类型,从而提高了代码的灵活性和可维护性。
容器构件在组合模式中用于表示复合节点,它们可以包含一组子节点,并递归地构建层次结构。容器构件的作用是允许创建具有多层次组织结构的对象,使得客户端可以一致地处理整个结构。这种模式非常适合用于表示树形结构的问题,如文件系统、图形界面中的控件和容器等。容器构件帮助实现了"部分-整体"的层次结构,提高了系统的可扩展性和灵活性。
🔎2.示例
命名空间CompositePattern包含文件系统FileSystem基类充当抽象构件类,文件File类充当叶子构件,文件夹Folder类充当容器构件,文件无效操作FileInvalidException 类进行异常处理。本案例尝试通过一个简单的文件系统来向大家阐述组合模式在调用方使用一致性方面的表现。
public abstract class FileSystem {
protected string _name = null;
protected const char SPLIT_CHAR_FILE = ' ';
protected const char SPLIT_CHAR_DIR = '▼';
public FileSystem(string name) {
this._name = name;
}
public abstract FileSystem Attach(FileSystem component);
public abstract FileSystem Detach(FileSystem component);
public abstract void Print(int depth = 0);
}
抽象构件,文件系统FileSystem类。
public class File : FileSystem {
public File(string name) : base(name) {
}
public override FileSystem Attach(FileSystem component) {
throw new FileInvalidException(
"You can not attach a component in a file!");
}
public override FileSystem Detach(FileSystem component) {
throw new FileInvalidException(
"You can not detach a component from a file!");
}
public override void Print(int depth = 0) {
Console.WriteLine(new string(SPLIT_CHAR_FILE, depth) + _name);
}
}
叶子构件,文件File类。
public class Folder : FileSystem {
private List<FileSystem> _childrens = null;
public Folder(string name) : base(name) {
_childrens = new List<FileSystem>();
}
public override FileSystem Attach(FileSystem component) {
_childrens.Add(component);
return this;
}
public override FileSystem Detach(FileSystem component) {
_childrens.Remove(component);
return this;
}
public override void Print(int depth = 0) {
Console.WriteLine(new string(SPLIT_CHAR_DIR, depth) + _name);
foreach (var component in _childrens) {
component.Print(depth + 1);
}
}
}
容器构件,文件夹Folder类。
public class FileInvalidException : Exception {
public FileInvalidException(string message) : base(message) {
}
public FileInvalidException(string message, Exception innerException)
: base(message, innerException) {
}
}
无效的文件操作FileInvalidException类进行简单的异常处理。
public class Program {
public static void Main(string[] args) {
try {
var root = new Folder("Root");
var music = new Folder("My Music");
music.Attach(new File("Heal the world.mp3"))
.Attach(new File("When You Say Nothing At All.mp3"))
.Attach(new File("Long Long Way to Go.mp3"))
.Attach(new File("Beautiful In White.mp3"));
var video = new Folder("My Video");
video.Attach(new File("The Shawshank Redemption.avi"))
.Attach(new File("Schindler's List.avi"))
.Attach(new File("Brave Heart.avi"));
var png = new File("missing.png");
root.Attach(png)
.Detach(png)
.Detach(png);
root.Attach(new File("readme.txt"))
.Attach(new File("index.html"))
.Attach(new File("file.cs"))
.Attach(music)
.Attach(video)
.Print();
png.Attach(new File("error.json"));
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
以上是调用方的代码,以下是这个案例的输出结果:
Root
readme.txt
index.html
file.cs
▼My Music
Heal the world.mp3
When You Say Nothing At All.mp3
Long Long Way to Go.mp3
Beautiful In White.mp3
▼My Video
The Shawshank Redemption.avi
Schindler's List.avi
Brave Heart.avi
You can not attach a component in a file!
🚀总结
🔎1.优点
组合模式(Composite Pattern)具有多个优点,主要包括:
简化客户端代码:由于叶子和容器对象在结构上实现了一致性,客户端可以统一对待它们,无需区分这是单一对象还是组合的对象。
清晰的层次结构:组合模式清晰地定义了部分和整体的层次结构,使得可以一致地组织和管理单个对象及其组合。
扩展性:新增类型的组件或修改现有的组件都相对容易,因为这些组件都遵循同一组抽象构件的接口。
方便添加新类型的叶子和容器:由于所有的叶子和容器对象使用共同的接口,可以很方便地添加新的叶子和容器,满足开放-封闭原则。
统一的处理方式:无论是叶子还是容器,处理方式都是统一的,这大大简化了对对象结构的操作和管理。
更高的复用性:由于叶子和容器对象都遵循同一接口,这些对象更容易在其他场合复用。
组合模式的优点使其成为处理树形结构和部分-整体关系的理想选择,广泛应用于如图形系统、GUI工具、文件系统等场景。递归调用**:由于容器可以包含其他容器,这允许在组合模式中轻松地进行递归操作,例如遍历整个组合结构。
- 灵活性:组合模式不仅仅限于两层的部分-整体层次结构,它可以轻松地扩展到更深的层次结构。
组合模式为处理具有部分-整体层次关系的结构提供了一个清晰、灵活和高效的方法。
🔎2.缺点
尽管组合模式(Composite Pattern)有许多优点,但也有一些缺点需要考虑:
复杂性增加:引入了组合模式会增加系统的复杂性,特别是在处理复杂的层次结构时。这可能会导致理解和维护代码变得更加困难。
不容易限制组件类型:抽象构件定义了一个统一的接口,但在具体实现时可能难以限制添加的组件类型。例如,如果只想让容器节点可以添加特定类型的子节点,可能需要在实现上增加额外的逻辑。
不适合每个场景:并非所有的场景都适合使用组合模式。如果系统中的对象不是树状结构,或者不需要以部分-整体的方式来处理对象,那么引入组合模式可能会带来不必要的复杂性。
可能导致性能损失:递归遍历复合结构时,可能会导致性能损失,特别是当结构非常庞大时。每次递归都会增加额外的开销。
可能不容易删除子节点:有时可能会在容器中添加了许多子节点后发现需要删除某些子节点,这可能不容易实现,因为并非所有的组合模式实现都提供了便捷的子节点删除机制。
组合模式虽然有很多优点,但需要谨慎使用,根据具体的情况来判断是否适合使用,以及如何实现以克服可能出现的缺点。可以为您提供关于组合模式的更多信息,或者回答您对编程或其他主题的任何问题。有什么我可以帮忙的
🔎3.使用场景
组合模式(Composite Pattern)适用于以下一些常见的场景:
树状结构:组合模式最常见的使用场景是处理树状结构,其中包含了部分-整体关系,例如文件系统、图形界面中的控件和窗口、组织结构图等。
部分-整体关系:当系统中的对象可以以部分-整体的层次结构组织,其中部分可以是单独的对象,也可以是包含其他部分和整体的容器对象时,组合模式非常有用。
统一接口:当您希望客户端能够以一致的方式处理单个对象和组合对象,无需关心对象的具体类型时,组合模式提供了一个适当的抽象层。
递归操作:如果您需要在整个层次结构中执行递归操作,例如遍历、查找、计算总和等,组合模式是一个很好的选择。
组织结构:组合模式非常适合用于表示组织结构,如公司的部门和员工关系,学校的班级和学生关系等。
图形编辑器:在图形编辑器中,可以使用组合模式来表示和操作图形对象,其中图形对象可以是简单的形状(叶子)或包含多个形状的组合(容器)。
菜单系统:在菜单系统中,可以使用组合模式来表示菜单项和子菜单的关系,从而实现多层次的菜单结构。
文件系统:组合模式可以用于表示文件系统中的文件和文件夹,其中文件夹可以包含文件和其他文件夹。
组合模式在处理具有层次结构的对象、部分-整体关系以及需要统一接口和递归操作的情况下非常有用。它能够帮助构建灵活的、可扩展的系统,同时使得客户端代码更加简洁和可维护。
🚀感谢:给读者的一封信
亲爱的读者,
我在这篇文章中投入了大量的心血和时间,希望为您提供有价值的内容。这篇文章包含了深入的研究和个人经验,我相信这些信息对您非常有帮助。
如果您觉得这篇文章对您有所帮助,我诚恳地请求您考虑赞赏1元钱的支持。这个金额不会对您的财务状况造成负担,但它会对我继续创作高质量的内容产生积极的影响。
我之所以写这篇文章,是因为我热爱分享有用的知识和见解。您的支持将帮助我继续这个使命,也鼓励我花更多的时间和精力创作更多有价值的内容。
如果您愿意支持我的创作,请扫描下面二维码,您的支持将不胜感激。同时,如果您有任何反馈或建议,也欢迎与我分享。
再次感谢您的阅读和支持!
最诚挚的问候, “愚公搬代码”
- 点赞
- 收藏
- 关注作者
评论(0)