【重走编程路】设计模式概述(七) -- 外观模式、组合模式、享元模式
文章目录
前言
结构型模式关注如何组合对象以形成更大的结构,提供了一种将对象组合成具有特定行为的结构的最佳方式。本章介绍结构型模式中的外观模式、组合模式和享元模式。
10. 外观模式(Facade)
定义
外观模式,又称门面模式,是一种结构型设计模式。它为子系统中的一组接口提供一个统一的接口,从而简化子系统的复杂性,使得外部系统只需与这个统一接口交互,而无需关心子系统内部的复杂细节。外观模式通过定义一个高层次的接口,将客户端与子系统的内部复杂性隔离开来,提高了系统的可维护性和易用性。
解决方案
外观模式通过引入一个外观类(Facade),将复杂的子系统接口进行封装,为客户端提供一个简单的高层接口。外观类充当了客户端与子系统之间的中间人,处理客户端的请求并将其转发给适当的子系统。外观模式并不在系统中添加新功能,它只是提供了一个更简洁的接口,以简化客户端的操作。
为什么使用外观模式
1. 简化接口: 通过为复杂的子系统提供一个简单的接口,降低了客户端的复杂度。
2. 减少系统间的依赖: 客户端只需与外观类交互,而无需直接依赖于子系统的多个类,从而减少了系统间的耦合。
3. 提高灵活性: 在不改变客户端代码的情况下,可以轻松地更改或扩展子系统的内部结构。
假设我们有一个复杂的图形编辑软件,它包含了多个子系统,如绘图子系统、颜色管理子系统和字体管理子系统。我们可以使用一个外观类来封装这些子系统的功能,并提供一个简单的接口给用户使用。
#include <iostream>
// 绘图子系统
class DrawingSystem {
public:
void drawCircle() {
std::cout << "Drawing a circle\n";
}
void drawRectangle() {
std::cout << "Drawing a rectangle\n";
}
};
// 颜色管理子系统
class ColorSystem {
public:
void setForegroundColor(const std::string& color) {
std::cout << "Setting foreground color to " << color << "\n";
}
};
// 字体管理子系统
class FontSystem {
public:
void setFont(const std::string& font) {
std::cout << "Setting font to " << font << "\n";
}
};
// 外观类
class GraphicsFacade {
private:
DrawingSystem* drawingSystem;
ColorSystem* colorSystem;
FontSystem* fontSystem;
public:
GraphicsFacade() : drawingSystem(new DrawingSystem()), colorSystem(new ColorSystem()), fontSystem(new FontSystem()) {}
~GraphicsFacade() {
delete drawingSystem;
delete colorSystem;
delete fontSystem;
}
void drawShapedText(const std::string& shape, const std::string& text, const std::string& color, const std::string& font) {
colorSystem->setForegroundColor(color);
fontSystem->setFont(font);
if (shape == "circle") {
drawingSystem->drawCircle();
}
else if (shape == "rectangle") {
drawingSystem->drawRectangle();
}
// 假设这里还有绘制文本的代码
std::cout << "Drawing text: " << text << "\n";
}
};
int main() {
GraphicsFacade facade;
facade.drawShapedText("circle", "Hello, World!", "blue", "Arial");
return 0;
}
应用场景
- 当一个系统非常复杂,客户端与多个子系统交互时,可以使用外观模式来简化交互。
- 当需要为复杂的子系统提供一个简单的接口时。
- 当客户端和子系统之间存在大量的依赖关系,需要降低它们之间的耦合度时。
优缺点
优点:
- 降低了客户端与子系统的耦合度。
- 简化了客户端代码,提高了系统的易用性。
- 提高了系统的灵活性和可扩展性,可以在不修改客户端代码的情况下更改子系统的实现。
缺点:
- 如果设计不当,可能会隐藏子系统的某些功能,导致客户端无法直接访问。
- 外观类可能会变得过于庞大,增加系统的复杂性。
- 过度使用外观模式可能会导致系统内部层次结构不清晰,增加理解和维护的难度。
11. 组合模式(Composite)
定义
组合模式是一种结构型设计模式,它将对象组合成树形结构以表示 “部分-整体” 的层次结构。在这种模式中,客户端代码可以一致地处理单个对象和组合对象,无需区分它们之间的差别。组合模式也被称为“整体-部分”模式。
解决方案
组合模式主要解决在对象组合中表示整体和部分关系的问题,提供了一种方式来创建对象的层次结构,其中部分可以像整体一样被处理。通过定义共同的接口或基类(Component),并在其中声明管理子对象的方法(如添加、删除、获取子对象等),无论是叶子节点(Leaf)还是容器节点(Composite)都实现了这个接口或继承了这个基类。容器节点可以包含其他容器节点或叶子节点,从而形成一个树形结构。
// 文件系统
#include <iostream>
#include <vector>
#include <memory>
class FileSystemComponent {
public:
virtual ~FileSystemComponent() {}
virtual void display(int level = 0) const = 0;
protected:
std::string name;
// 文件基类,不允许外部创建
FileSystemComponent(const std::string& name) : name(name) {}
};
class File : public FileSystemComponent {
public:
File(const std::string& name) : FileSystemComponent(name) {}
void display(int level = 0) const override {
for (int i = 0; i < level; ++i) {
std::cout << " ";
}
std::cout << "File: " << name << std::endl;
}
};
class Folder : public FileSystemComponent {
public:
Folder(const std::string& name) : FileSystemComponent(name) {}
void add(std::shared_ptr<FileSystemComponent> component) {
components.push_back(component);
}
void display(int level = 0) const override {
for (int i = 0; i < level; ++i) {
std::cout << " ";
}
std::cout << "Folder: [" << name << "]" << std::endl;
for (const auto& comp : components) {
comp->display(level + 1);
}
}
private:
std::vector<std::shared_ptr<FileSystemComponent>> components;
};
int main() {
// 创建文件和文件夹
std::shared_ptr<File> file1 = std::make_shared<File>("File1.txt");
std::shared_ptr<File> file2 = std::make_shared<File>("File2.txt");
std::shared_ptr<Folder> folder1 = std::make_shared<Folder>("Folder1");
// 将文件添加到文件夹
folder1->add(file1);
folder1->add(file2);
// 创建另一个文件夹并添加文件和子文件夹
std::shared_ptr<Folder> folder2 = std::make_shared<Folder>("Folder2");
folder2->add(file1);
folder2->add(folder1);
// 显示整个文件系统结构
folder2->display();
return 0;
}
应用场景
- 表示对象的部分-整体层次结构: 如文件和文件夹系统、公司的组织结构等。
- 菜单系统: 菜单项可以包含子菜单项,形成层次结构。
- 图形界面(GUI): 表示窗口、按钮和菜单项等控件的树形结构。
- XML文档解析: XML文档中的元素可以包含子元素和文本内容,形成嵌套结构。
优缺点
优点:
- 简化客户端代码: 客户端可以使用相同的代码处理单个对象和组合对象。
- 较高的可扩展性: 容易在组合体内增加新的对象,客户端代码不需要修改。
- 容易创建复杂的层次结构: 客户端无需知道组合的内部结构,就可以轻松构建复杂的树形结构。
缺点:
- 设计复杂: 类层次结构可能变得复杂,增加了理解和维护的难度。
- 性能问题: 在处理大型树形结构时,递归操作可能导致性能问题。
- 接口限制: 要求所有组件都实现相同的接口,限制了组件的特异性。如果某些组件需要特殊的操作或属性,而这些操作或属性不适用于其他组件,那么将它们强制实现相同的接口可能会导致设计上的不合理或冗余。
12. 享元模式(Flyweight)
定义
享元模式旨在通过共享尽可能多的相似对象来减少内存中的对象数量,从而提高程序的效率和性能。在享元模式中,会区分出内部状态(不随环境改变而改变)和外部状态(随环境改变而改变),并通过共享内部状态来减少对象的数量。享元对象自身通常很小且不可变,以支持高效的共享。
解决方案
享元模式的解决方案通常包含以下几个关键部分:
- 享元接口(Flyweight): 定义一个接口,通过这个接口可以访问享元对象的内部状态,并可能允许设置外部状态。
- 具体享元类(Concrete Flyweight): 实现享元接口,为内部状态提供存储空间。如果享元类需要外部状态,则必须通过构造函数或特定方法将其传入。
- 享元工厂类(Flyweight Factory): 负责创建和管理享元对象。为了确保享元对象的唯一性,享元工厂类通常会使用一个存储结构(如哈希表)来存储已经创建的享元对象。
- 客户端(Client): 在需要使用享元对象时,通过享元工厂获取它们,并设置外部状态(如果需要的话)。
假设我们有一个文本编辑器,需要频繁地显示和编辑不同字体和大小的文本。我们可以使用享元模式来共享具有相同字体和大小的文本对象,而仅将文本内容作为外部状态传递。
#include <iostream>
#include <unordered_map>
#include <string>
// 享元接口
class Flyweight {
public:
virtual ~Flyweight() {}
virtual void operation(std::string extrinsicState) = 0;
};
// 具体享元类,代表具有特定字体和大小的文本
class ConcreteFlyweight : public Flyweight {
private:
std::string intrinsicState; // 内部状态,如字体和大小
public:
ConcreteFlyweight(const std::string& state) : intrinsicState(state) {}
void operation(std::string extrinsicState) override {
std::cout << "Intrinsic: " << intrinsicState << ", Extrinsic: " << extrinsicState << std::endl;
}
};
// 享元工厂类
class FlyweightFactory {
private:
std::unordered_map<std::string, Flyweight*> flyweights;
public:
Flyweight* getFlyweight(const std::string& key) {
if (flyweights.find(key) == flyweights.end()) {
flyweights[key] = new ConcreteFlyweight(key);
}
return flyweights[key];
}
};
// 客户端
int main() {
FlyweightFactory factory;
// 获取具有相同内部状态的享元对象
Flyweight* f1 = factory.getFlyweight("Arial-12");
Flyweight* f2 = factory.getFlyweight("Arial-12");
// 设置外部状态并执行操作
f1->operation("Hello, World!");
f2->operation("Another text.");
// 验证f1和f2是否指向同一对象
std::cout << "f1 and f2 are the same object: " << (f1 == f2) << std::endl; // 应输出 1 (true)
return 0;
}
应用场景
享元模式适用于以下场景:
- 当一个应用程序使用了大量的对象,而这些对象的大量数据可以外部化时。
- 当对象的多数状态可以外部化,并且这些对象可以被多个客户端共享时。
- 当存储大量细粒度对象的开销过高,需要节省内存时。
优缺点
优点:
- 减少了对象的数量,降低了内存占用。
- 提高了效率,因为减少了创建和销毁对象的开销。
- 外部状态使得对象可以在不同上下文中重用。
缺点:
- 增加了程序的复杂度,因为需要区分内部状态和外部状态。
- 如果不恰当地使用外部状态,可能会导致程序错误。
- 如果享元对象过多,享元工厂的管理可能会变得复杂。
…
To be continued.
原文地址:https://blog.csdn.net/weixin_44595767/article/details/140342407
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!