【重走编程路】设计模式概述(七) -- 外观模式、组合模式、享元模式

文章目录

  • 前言
  • 10. 外观模式(Facade)
    • 定义
    • 解决方案
    • 为什么使用外观模式
    • 应用场景
    • 优缺点
  • 11. 组合模式(Composite)
    • 定义
    • 解决方案
    • 应用场景
    • 优缺点
  • 12. 享元模式(Flyweight)
    • 定义
    • 解决方案
    • 应用场景
    • 优缺点


前言

结构型模式关注如何组合对象以形成更大的结构,提供了一种将对象组合成具有特定行为的结构的最佳方式。本章介绍结构型模式中的外观模式、组合模式和享元模式。


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;
}

应用场景

  1. 当一个系统非常复杂,客户端与多个子系统交互时,可以使用外观模式来简化交互。
  2. 当需要为复杂的子系统提供一个简单的接口时。
  3. 当客户端和子系统之间存在大量的依赖关系,需要降低它们之间的耦合度时。

优缺点

优点:

  • 降低了客户端与子系统的耦合度。
  • 简化了客户端代码,提高了系统的易用性。
  • 提高了系统的灵活性和可扩展性,可以在不修改客户端代码的情况下更改子系统的实现。

缺点:

  • 如果设计不当,可能会隐藏子系统的某些功能,导致客户端无法直接访问。
  • 外观类可能会变得过于庞大,增加系统的复杂性。
  • 过度使用外观模式可能会导致系统内部层次结构不清晰,增加理解和维护的难度。

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;  
}

应用场景

  1. 表示对象的部分-整体层次结构: 如文件和文件夹系统、公司的组织结构等。
  2. 菜单系统: 菜单项可以包含子菜单项,形成层次结构。
  3. 图形界面(GUI): 表示窗口、按钮和菜单项等控件的树形结构。
  4. XML文档解析: XML文档中的元素可以包含子元素和文本内容,形成嵌套结构。

优缺点

优点:

  • 简化客户端代码: 客户端可以使用相同的代码处理单个对象和组合对象。
  • 较高的可扩展性: 容易在组合体内增加新的对象,客户端代码不需要修改。
  • 容易创建复杂的层次结构: 客户端无需知道组合的内部结构,就可以轻松构建复杂的树形结构。

缺点:

  • 设计复杂: 类层次结构可能变得复杂,增加了理解和维护的难度。
  • 性能问题: 在处理大型树形结构时,递归操作可能导致性能问题。
  • 接口限制: 要求所有组件都实现相同的接口,限制了组件的特异性。如果某些组件需要特殊的操作或属性,而这些操作或属性不适用于其他组件,那么将它们强制实现相同的接口可能会导致设计上的不合理或冗余。

12. 享元模式(Flyweight)

定义

享元模式旨在通过共享尽可能多的相似对象来减少内存中的对象数量,从而提高程序的效率和性能。在享元模式中,会区分出内部状态(不随环境改变而改变)外部状态(随环境改变而改变),并通过共享内部状态来减少对象的数量。享元对象自身通常很小且不可变,以支持高效的共享。

解决方案

享元模式的解决方案通常包含以下几个关键部分:

  1. 享元接口(Flyweight): 定义一个接口,通过这个接口可以访问享元对象的内部状态,并可能允许设置外部状态。
  2. 具体享元类(Concrete Flyweight): 实现享元接口,为内部状态提供存储空间。如果享元类需要外部状态,则必须通过构造函数或特定方法将其传入。
  3. 享元工厂类(Flyweight Factory): 负责创建和管理享元对象。为了确保享元对象的唯一性,享元工厂类通常会使用一个存储结构(如哈希表)来存储已经创建的享元对象。
  4. 客户端(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;  
}

应用场景

享元模式适用于以下场景:

  1. 当一个应用程序使用了大量的对象,而这些对象的大量数据可以外部化时。
  2. 当对象的多数状态可以外部化,并且这些对象可以被多个客户端共享时。
  3. 当存储大量细粒度对象的开销过高,需要节省内存时。

优缺点

优点:

  • 减少了对象的数量,降低了内存占用。
  • 提高了效率,因为减少了创建和销毁对象的开销。
  • 外部状态使得对象可以在不同上下文中重用。

缺点:

  • 增加了程序的复杂度,因为需要区分内部状态和外部状态。
  • 如果不恰当地使用外部状态,可能会导致程序错误。
  • 如果享元对象过多,享元工厂的管理可能会变得复杂。

To be continued.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/46665.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python百日进阶-Web开发-音频】Day702 - librosa安装及模块一览表

文章目录 一、Librosa简介与安装1.1 Librosa是什么1.2 Librosa官网 二、Librosa安装2.1 安装Librosa 三、安装ffmpeg3.1 ffmpeg官网下载3.2 ffmpeg安装3.2.1 解压3.2.2 添加环境变量3.2.3 测试ffmpeg是否安装成功 四、Librosa 库模块一览4.1 库函数结构4.2 Audio processing&am…

C++相关概念和易错语法(21)(虚函数、协变、析构函数的重写)

多态的核心是虚函数&#xff0c;本文从虚函数出发&#xff0c;根据原理慢慢推进得到结论&#xff0c;进而理解多态 1.虚函数 先看一下下面的代码&#xff0c;想想什么导致了这个结果 #include <iostream> using namespace std;class A { public:virtual void test(){co…

元服务体验-服务发现

服务发现&#xff0c;无论线上或线下的方式都可以发现元服务。 线上&#xff1a;基于用户意图。从精准意图的搜索、用户事件触发的推荐到主动探索等场景。用户可以在设备的负一屏、全局搜索、应用市场、桌面等场景发现元服务。 线下&#xff1a;用户在 HarmonyOS Connect标签…

华为HCIP Datacom H12-821 卷39

1.填空题 请2001 :0DB8:0000:C030:0000: 000: 09A0:CDEF地址进行压缩。() (若答案中存在字母&#xff0c;请采用大写格式) 参考答案&#xff1a;2001 :DB8:0:C030: :9A0:CDEF 解析&#xff1a; IPv6地址的表示方法 IPv6地址总长度为128比特&#xff0c;通常分为8组&#xff0c…

LeetCode 20.有效的括号 C写法

LeetCode 20.有效的括号 C写法 思路&#x1f9d0;&#xff1a; ​ 这题最优思路是用栈来进行匹配&#xff0c;如果是左括号就入栈&#xff0c;如果是右括号那么左括号就出栈去匹配&#xff0c;匹配成功就继续入栈或者出栈&#xff0c;匹配失败则字符串无效。不过C语言没有STL…

win10远程ubuntu服务器桌面且显示图像窗口工具及配置说明

仅需一个MobaXterm_Personal工具就可以实现 网上的教程比较多&#xff0c;实现起来比较复杂&#xff0c;这个是经过自己的钻研找到的方法&#xff08;请勿转载和抄袭&#xff09; 报错&#xff1a;cannot connect to X server :0.0 操作1&#xff1a;export DISPLAY自己windo…

SSE、Webworker 、webSocket、Http、Socket 服务器推送技术

Http协议 受浏览器的同源策略限制 HTTP 协议是一种无状态的、无连接&#xff08;短暂连接&#xff0c;客户端发送请求&#xff0c;服务器响应后即断开连接&#xff09;的、单向的应用层协议。 它采用了请求/响应模型。通信请求只能由客户端发起&#xff0c;服务端对请求做出应…

IP地址与物理地址:网络通信的基础详解

在学习网络通信时&#xff0c;理解IP地址与物理地址&#xff08;也称为硬件地址&#xff09;的区别至关重要。这篇文章将为你解答这些基本概念&#xff0c;并帮助你更好地掌握网络通信的基础。 什么是IP地址和物理地址&#xff1f; IP地址是网络层的逻辑地址&#xff0c;用于标…

leetcode算法题(反转链表)

思路1&#xff1a; 创建新的链表&#xff0c;遍历原链表&#xff0c;将原链表的节点进行头插到新链表中。 struct ListNode* reverseList(struct ListNode* head) {struct ListNode* next NULL;struct ListNode* new_head NULL;if (head NULL ||head->next NULL) // 空…

Python 获取今天(当天)、昨天(前一天)、前天(昨天的前一天)的开始时间、结束时间

描述&#xff1a;我这里是封装成DatetimeHelper工具类来调用 1.今天(当天)开始时间、结束时间 from datetime import datetime, timedeltaclass DatetimeHelper:# 获取今天(当天)的开始时间、结束时间(datetime类型)staticmethoddef getTodayStartEnd():# 获取当前的日期now …

在 electron+vite+vue3+express 项目中使用better-sqlite3

文章目录 一、安装 electron-rebuild 和 better-sqlite3二、使用 electron-rebuild 重建 Node.js 模块三、better-sqlite3 的基本使用四、打包五、参考资料 一、安装 electron-rebuild 和 better-sqlite3 yarn add -D electron-rebuild yarn add better-sqlite3Electron 内置的…

解决onlyoffice无法重命名的问题

当前的问题&#xff1a; 返回的是 error&#xff1a;1&#xff0c;根据官方文档的解释&#xff0c;这个是文档的key是错误的。 参考官方文档&#xff1a;https://api.onlyoffice.com/zh/editors/command 解决思路&#xff1a;看有没有什么事件&#xff0c;能够携带文档的key…

无人机图像目标检测

本仓库是人工智能课程的课程作业仓库&#xff0c;主要是完成无人机图像目标检测的任务&#xff0c;我们对visdrone数据集进行了处理&#xff0c;在yolo和ssd两种框架下进行了训练和测试&#xff0c;并编写demo用于实时的无人机图像目标检测。 requirements依赖&#xff1a; ss…

01- 收入数据集【Pytorch入门实战】

目录 一、机器学习基础 二、实战例子 1.数据集分析 2.实战训练 3.总结 三、参考资料 一、机器学习基础 为了解决这个问题&#xff0c;人们想到数据驱动方法&#xff0c;也就是让计算机从现有的大量的带标签图片电学习规律&#xff0c;一旦计算机学习到了其中的规律&…

LLM量化--AWQ论文阅读笔记

写在前面&#xff1a;近来大模型十分火爆&#xff0c;所以最近开启了一波对大模型推理优化论文的阅读&#xff0c;下面是自己的阅读笔记&#xff0c;里面对文章的理解并不全面&#xff0c;只将自己认为比较重要的部分摘了出来&#xff0c;详读的大家可以参看原文 原论文地址&am…

PostgreSQL 中如何处理数据的并发插入和唯一约束的冲突解决?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 PostgreSQL 中如何处理数据的并发插入和唯一约束的冲突解决一、并发插入和唯一约束的基本概念&#xf…

微服务实战系列之玩转Docker(一)

前言 话说计算机的“小型化”发展&#xff0c;历经了大型机、中型机直至微型机&#xff0c;贯穿了整个20世纪的下半叶。同样&#xff0c;伴随着计算机的各个发展阶段&#xff0c;如何做到“资源共享、资源节约”&#xff0c;也一直是一代又一代计算机人的不懈追求和历史使命。今…

bash: ip: command not found

输入&#xff1a; ip addr 报错&#xff1a; bash: ip: command not found 报错解释&#xff1a; 这个错误表明在Docker容器中尝试执行ip addr命令时&#xff0c;找不到ip命令。这通常意味着iproute2包没有在容器的Linux发行版中安装或者没有正确地设置在容器的环境变量PA…

HTTP背后的故事:理解现代网络如何工作的关键(二)

一.认识请求方法(method) 1.GET方法 请求体中的首行包括&#xff1a;方法&#xff0c;URL&#xff0c;版本号 方法描述的是这次请求&#xff0c;是具体去做什么 GET方法&#xff1a; 1.GET 是最常用的 HTTP 方法. 常用于获取服务器上的某个资源。 2.在浏览器中直接输入 UR…

算法 —— 快速幂

目录 P1045 [NOIP2003 普及组] 麦森数 P1226 【模板】快速幂 原理I 原理II P1226 代码解析 P1045 代码解析 P1045 [NOIP2003 普及组] 麦森数 本题来自洛谷&#xff1a;P1045 [NOIP2003 普及组] 麦森数&#xff0c;根据题意&#xff0c;我们可以看到本题需要计算最少2的1…