张家口市网站建设_网站建设公司_测试工程师_seo优化
2025/12/22 14:50:52 网站建设 项目流程

4.结构型模式

让类和类进行组合,获得更大的结构。

4.1 代理模式

  代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

  • 标红重点概念

代理模式是 “为其他对象提供一种代理,以控制对这个对象的访问”。

  • 模式本质

不允许直接访问 “真实对象”,通过 “代理对象” 作为中间层,实现对真实对象的访问限制(如权限验证、访问拦截等)。

  • 核心目的

解耦 “访问控制逻辑” 与 “真实对象的业务逻辑”(如真实对象仅负责 “启动系统”,代理负责 “谁能启动系统”)。

4.1.1模式中的角色和职责

subject(抽象主题角色):真实主题与代理主题的共同接口。

RealSubject(真实主题角色):定义了代理角色所代表的真实对象。

Proxy(代理主题角色):含有对真实主题角色的引用,代理角色通常在将客户端调用传递给真是主题对象之前或者之后执行某些操作,而不是单纯返回真实的对象。

4.1.2 代理模式的案例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;//商品类
class Goods
{
public:Goods(bool IsReal, string GoodsName) :m_IsReal(IsReal), m_GoodsName(GoodsName){}bool getIsReal(){return m_IsReal;}string getGoodsName(){return m_GoodsName;}
private:bool m_IsReal;  //商品真假string m_GoodsName; //商品名称
};//购物抽象
class AbstractShopping
{
public:virtual void BuyGoods(Goods*) = 0; 
};//韩国购物
class KoreaShopping : public AbstractShopping
{
public:virtual void BuyGoods(Goods* goods){cout << "在韩国购买" << goods->getGoodsName() << endl;}
};//美国购物
class AmericanShopping : public AbstractShopping
{
public:virtual void BuyGoods(Goods* goods){cout << "在美国购买" << goods->getGoodsName() << endl;}
};//非洲购物
class AfricanShopping : public AbstractShopping
{
public:virtual void BuyGoods(Goods* goods){cout << "在非洲购买" << goods->getGoodsName() << endl;}
};//自己购物
void test01()
{AbstractShopping* shopping = NULL;//去美国买啤酒Goods* bear = new Goods(true, "啤酒");shopping = new AmericanShopping;shopping->BuyGoods(bear);delete bear;delete shopping;//去韩国买化妆品Goods* cosmetics = new Goods(true, "化妆品");shopping = new KoreaShopping;shopping->BuyGoods(cosmetics);delete cosmetics;delete shopping;Goods* ivory = new Goods(true, "象牙");shopping = new AfricanShopping;shopping->BuyGoods(ivory);delete ivory;delete shopping;
}//现在我不想自己去购物了  自己需要花费路费 还有自己办理货品海关检查 自己辨别商品真伪
//海外代购 帮助检查商品真伪 海关检查 
class OverseasShopping : public AbstractShopping
{
public:OverseasShopping(AbstractShopping* mode){pShoppingMode = mode;  //购物模式  去韩国买  还是去美国买 还是去非洲买}virtual void BuyGoods(Goods* goods){if (GoodsIsReal(goods))//如果产品是真的{ CheckGoods(); //海关检查pShoppingMode->BuyGoods(goods);}else{cout << goods->getGoodsName() << "是假商品,放弃购买!" << endl;}delete goods;}//辨别商品真伪bool GoodsIsReal(Goods* goods){cout << "海外代理检查 "<< goods->getGoodsName() << "货品真伪" << endl;return goods->getIsReal();}void CheckGoods(){cout << "海外代理商品海关检查" << endl;}~OverseasShopping(){if (NULL != pShoppingMode){delete pShoppingMode;}}
private:AbstractShopping* pShoppingMode;
};void test02()
{AbstractShopping* proxy = NULL;//创建一个去韩国购物的代理proxy = new OverseasShopping(new KoreaShopping);proxy->BuyGoods(new Goods(true,"化妆品"));delete proxy;cout << "-----------------------" << endl;//创建一个去美国买啤酒的代理proxy = new OverseasShopping(new AmericanShopping);proxy->BuyGoods(new Goods(false, "啤酒"));delete proxy;cout << "-----------------------" << endl;//创建一个去非洲买啤象牙的代理proxy = new OverseasShopping(new AfricanShopping);proxy->BuyGoods(new Goods(true, "象牙"));delete proxy;
}int main()
{//test01();test02();system("pause");return EXIT_SUCCESS;
}

(1)代码演进解析(C++ 实现)

课程通过 “基础版本→代理优化版本” 的对比,逐步讲解代理模式的必要性与实现逻辑。

①版本 1:基础实现(存在缺陷)

代码结构

#include 
using namespace std;// 抽象接口:约束共有行为(启动系统)
class AbstractCommonInterface 
{
public:virtual void run() = 0; // 纯虚函数,强制子类实现
};// 真实对象:已完成的系统(MySystem)
class MySystem : public AbstractCommonInterface {
public:virtual void run(){ cout << "系统启动..." << endl;}
};// 主函数:直接访问真实对象
int main() 
{MySystem* system = new MySystem;system->run(); // 直接调用真实对象的run方法return 0;
}

存在问题

  • 无访问控制:任何人都能通过new MySystem()直接调用run()启动系统,不符合 “仅有权限用户可启动” 的实际需求;

  • 耦合严重:若需增加权限验证,需修改MySystem类的代码,违背 “开闭原则”(对修改关闭,对扩展开放)。

②版本 2:代理模式优化(核心实现)

优化思路

  • 新增 “代理类”MySystemProxy,代理类与真实类MySystem共同继承抽象接口(保证行为一致);

  • 代理类封装 “真实对象” 与 “访问控制逻辑”,用户仅通过代理类访问真实对象。

完整代码

#include <iostream>
#include > // 需包含字符串头文件(课程代码隐含)
using namespace std;// 抽象接口:约束真实类与代理类的共有行为
class AbstractCommonInterface {
public:virtual void run() = 0;
};// 真实对象:已完成的系统(业务逻辑不变)
//已经写好的系统
class MySystem : public AbstractCommonInterface
{
public:virtual void run(){ cout << "系统启动..." << endl;}
};// 代理类:控制对MySystem的访问(核心新增)
class MySystemProxy : public AbstractCommonInterface 
{
public:// 构造函数:初始化用户名、密码,并创建真实对象实例MySystemProxy(string mUsername, string mPassword) {this->mUsername = mUsername;this->mPassword = mPassword;pSystem = new MySystem; // 代理内部封装真实对象}// 析构函数:释放真实对象内存(避免内存泄漏)~MySystemProxy() {if (pSystem != NULL) { delete pSystem; }}// 访问控制逻辑:验证用户名密码bool checkUsernameAndPassword() {// 仅“admin/admin”为合法权限if (mUsername == "admin" && mPassword == "admin") { return true; }return false;}// 代理的核心方法:先验证权限,再决定是否调用真实对象virtual void run() {  if (checkUsernameAndPassword()){ cout <密码正确,验证通过!" <->pSystem->run(); // 权限通过:调用真实对象的启动方法} else { cout <密码错误,权限不足..." <失败:拦截访问}}public:MySystem* pSystem;    // 封装真实对象(用户无法直接访问)string mUsername;     // 存储用户传入的用户名string mPassword;     // 存储用户传入的密码
};// 测试函数:通过代理访问系统
void test01() 
{// 场景1:传入正确权限(admin/admin)MySystemProxy* proxy1 = new MySystemProxy("admin", "admin");proxy1->run(); // 输出:用户名、密码正确,验证通过! → 系统启动...// 场景2:传入错误权限(如admin/123)MySystemProxy* proxy2 = new MySystemProxy("admin", "123");proxy2->run(); // 输出:用户名错误或者密码错误,权限不足...
}int main() 
{test01();return 0;
}

关键逻辑解析

  1. 接口的作用:AbstractCommonInterface强制MySystem(真实类)和MySystemProxy(代理类)都实现run(),保证 “用户访问代理时,与访问真实对象的接口一致”;
  2. 访问控制流程:用户调用代理run() → 先执行checkUsernameAndPassword()验证 → 验证通过则调用真实对象run(),否则拦截。

(2)关键类比:辅助理解代理关系

类比角色 对应代码组件 核心行为 / 作用
工厂 MySystem(真实对象) 生产商品(提供真实业务逻辑:启动系统)
超市 / 专卖店 MySystemProxy(代理) 售卖商品(提供访问入口:代理启动系统)
用户 代码中的 test01 () 通过超市买商品(通过代理访问系统)
共有的 “卖货” 行为 AbstractCommonInterface 接口 保证工厂和超市都能 “提供商品”(真实类和代理类都有 run ())

(3)核心要点总结

  1. 接口是基础:抽象接口(如AbstractCommonInterface)是代理模式的前提,保证真实类与代理类的行为一致性;

  2. 代理的核心职责

    • 封装真实对象(隐藏真实对象的直接访问);

    • 实现访问控制(如权限验证、日志记录、访问拦截等);

  3. 解耦价值:真实类仅负责业务逻辑(如MySystem只管 “启动系统”),访问控制逻辑放在代理类,后续需修改权限规则时,仅改代理类即可;

  4. 用户认知:对用户而言,代理类就是 “真实对象”(如用户调用代理的run(),以为直接操作系统),但实际内部由代理转发请求;

  5. 适用场景:需对真实对象进行访问限制(如权限、频率)、或需在访问前后增加额外逻辑(如日志、缓存)时,优先使用代理模式。

核心映射:用户不会直接去工厂买货(直接访问真实对象),而是通过超市(代理)购买,超市可控制 “卖给谁”(权限验证)。

(4)现实中的例子

①代理模式核心应用场景
场景 1:代理服务器(解决 “无法直接访问目标对象” 问题)

A.背景与问题​

  • 部分目标服务器(如国外的 YouTube 服务器)因政策限制,国内用户无法直接访问(直接请求会被屏蔽);

  • 核心需求:通过中间层间接访问目标服务器,同时不改变用户的请求逻辑。

B.代理角色与工作流程​

角色 职责
用户(客户端) 发起访问请求(如访问 YouTube),但不直接对接目标服务器
代理服务器 位于可访问目标服务器的环境(如国外),接收用户请求后,中转访问目标服务器
目标服务器 存储用户需要的资源(如 YouTube 的视频内容)

工作流程

用户 → 代理服务器(国内可访问)→ 目标服务器(如 YouTube,代理可访问)→ 代理服务器 → 用户

C.核心价值​

  • 突破 “直接访问限制”:代理作为 “桥梁”,连接客户端与无法直接对接的目标对象;

  • 隐藏目标对象细节:用户无需知道目标服务器的真实地址或访问规则,只需对接代理。

场景 2:缓冲服务器(解决 “目标对象负荷过重” 问题)

A.背景与问题​

  • Web 服务器(存储网页资源)需处理大量用户请求:每个请求需经历 “解析参数→查询数据库→组织数据→返回结果” 流程;

  • 当访问量过大时,Web 服务器负荷剧增,导致用户网页加载缓慢或无法打开。

B.代理角色与工作流程​

角色 职责
用户(客户端) 发起网页访问请求,优先对接缓冲服务器
缓冲服务器 作为 Web 服务器的代理,缓存常用网页数据,减少 Web 服务器的重复计算
Web 服务器 存储完整网页资源,仅在缓冲服务器无最新数据时处理请求

工作流程(分两种情况)

a.缓冲服务器有最新数据:

用户 → 缓冲服务器 → 直接返回缓存的网页数据(无需访问 Web 服务器);

b.缓冲服务器无最新数据(或数据过期):

用户 → 缓冲服务器 → 访问 Web 服务器(获取最新数据)→ 缓冲服务器缓存数据 → 返回数据给用户。

C.核心价值​

  • 减轻目标对象负荷:Web 服务器无需处理重复请求(如同一网页的多次访问),减少 “解析参数、查库” 等耗时操作;
  • 提升响应效率:用户从缓冲服务器获取数据,加载速度更快。
②本节课核心总结
  1. 代理模式的核心逻辑:通过中间层(代理)控制对真实对象的访问,不改变真实对象的核心功能;
  2. 两大典型应用场景:
    • 访问控制:代理服务器解决 “无法直接访问目标对象” 问题;
    • 负荷减轻:缓冲服务器减少真实对象(如 WebServer)的重复工作,提升效率;
  3. 设计关键:代理类需持有真实对象的引用,实现与真实对象一致的接口,确保客户端无感知切换。

4.1.3代理(Proxy)模式的概念与典型使用场景

(1)代理的基本思想(本节课核心)

  • 代理是什么

    • 当你(客户端)不能/不方便/不允许直接访问某个对象(目标对象、真实服务)时,引入一个“中间人”来替你完成访问,这个中间人就是代理
  • 代理解决的本质问题

    • 隔离与控制访问:把“访问目标对象”这件事交给代理来做,由代理决定是否访问、如何访问、访问前后要不要做额外处理。
    • 间接访问:你访问的是代理,但最终效果像是在访问目标对象。

(2)场景一:代理服务器(用于“绕过限制/间接访问”)

①场景描述
  • 有些网站/服务(例如在国外的某些服务器)在本地网络环境下无法直接访问(被封/被限制/不允许直连)。
  • 你如果直接请求目标服务器,会失败。
②代理如何工作(讲课中的流程)
  • 你(客户端)先把请求发给**代理服务器**
  • 代理服务器再去访问目标网站/服务(例如国外站点)
  • 代理拿到结果后再返回给你
③这个代理的关键点
  • “你到目标”不通,但“你到代理”通,同时“代理到目标”通
  • 所以通过代理实现了“间接可达”
  • 代理在这里的作用偏向:
    • 网络访问转发
    • 访问控制/规避直接限制
    • 隐藏真实访问者(一定程度上)

(3)场景二:缓冲(缓存)服务器(用于“减轻压力/提升性能”)

讲课里把它作为代理的一种用途:在 Web 访问中做“中间缓存”。

①为什么需要缓存代理

当用户很多时,如果所有人都直接访问 Web 服务器,会导致:

  • Web 服务器要频繁处理请求
  • 请求往往带参数(例如 URL 中 ?name=AA&time2=CC 这种)
  • 服务器必须进行一系列成较高的操作:
    • 解析请求参数
    • 根据参数判断业务需求
    • 查数据库取数据
    • 组织结果并返回页面
  • 人一多就会造成:
    • 服务器负荷大
    • 页面打开慢甚至打不开
②缓存代理如何工作(讲课中的逻辑)
  • 用户先访问缓冲/缓存服务器
  • 缓存服务器会把网页或数据提前缓存起来
  • 当用户请求某页面时:
    • 缓存命中(数据最新):直接由缓存服务器返回结果
      • 用户无需再让 Web 服务器解析参数、查库、组装数据
    • 缓存未命中/数据过旧:缓存服务器去访问真正的 Web 服务器
      • 拿到最新数据后更新缓存
      • 再把结果返回给用户
③这个代理的关键价值
  • 性能提升:大量请求被缓存挡住,响应更快
  • 减轻源站压力:Web 服务器和数据库的负担显著降低
  • 更稳定的服务体验:高并发下不容易崩

(4)本节课用两个例子总结“代理能做什么”

  • 代理服务器(转发访问)
    • 重点:解决不可直连/不允许直连的问题
    • 价值:让访问“绕一层”,实现间接访问
  • 缓存服务器(缓存代理)
    • 重点:解决访问量大导致服务器压力过大的问题
    • 价值:缓存热点内容,减少重复计算与查库,提高响应速度

(5)一句话收束(便于记忆)

  • 代理模式:当你不想或不能直接访问目标对象时,就让一个代理对象替你访问,并且代理还能在访问前后做额外工作(如转发、缓存、控制访问等)。

4.1.43模式的优缺点

优点:

  • 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。

缺点:

  • 代理实现较为复杂。

4.1.4 适用场景

  为其他对象提供一种代理以控制对这个对象的访问。

4.3外观模式

  根据迪米特法则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。

  Facade模式也叫外观模式,是由GoF提出的23种设计模式中的一种。Facade模式为一组具有类似功能的类群,比如类库,子系统等等,提供一个一致的简单的界面。这个一致的简单的界面被称作facade。

外观模式(Facade Pattern)概述

1. 模式定位

23 种经典设计模式之一,属于结构型模式

2. 核心作用

给一组功能关联的子系统(如类库、模块集群),提供一个统一、简单的接口(外观类),让客户端无需直接与复杂的子系统交互。

3. 解决的问题

以 “多子系统启动” 为例:

  • 无外观模式:客户端需要手动new每个子系统对象,逐个调用启动方法(需了解所有子系统细节)。
  • 有外观模式:客户端只需调用外观类的一个方法,由外观类内部封装子系统的创建与调用(无需了解子系统细节)。

4.3.1外观模式中角色和职责

Façade(外观角色):为调用方, 定义简单的调用接口。

SubSystem(子系统角色):功能提供者。指提供功能的类群(模块或子系统) 。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;/*
外观模式就是将复杂的子类系统抽象到同一个的接口进行管理
,外界只需要通过此接口与子类系统进行交互,而不必要直接与复杂的子类
系统进行交互
*///子系统1
class SubSystem1
{
public:void run(){ cout << "子系统一运行..." << endl; }
};
//子系统2
class SubSystem2
{
public:void run(){ cout << "子系统二运行..." << endl; }
};
//子系统3
class SubSystem3
{
public:void run(){ cout << "子系统三运行..." << endl; }
};
//子系统4
class SubSystem4
{
public:void run(){  cout << "子系统四运行..." << endl;  }
};//外观类
class Facede
{
public:Facede(){pSystem1 = new SubSystem1;pSystem2 = new SubSystem2;pSystem3 = new SubSystem3;pSystem4 = new SubSystem4;}void runSystem(){pSystem1->run();pSystem2->run();pSystem3->run();pSystem4->run();}private:SubSystem1* pSystem1;SubSystem2* pSystem2;SubSystem3* pSystem3;SubSystem4* pSystem4;
};void test01()
{Facede* facede = new Facede;facede->runSystem();
}int main()
{test01();system("pause");return EXIT_SUCCESS;
}

(1)子系统类

定义独立的子系统模块,各自实现自身功能:

// 子系统1
class SubSystem1 
{
public:void run(){ cout << "子系统一运行..." << endl; }
};
// 子系统2、3、4结构类似,略

(2)外观类

  • 内部创建所有子系统对象。
  • 提供统一方法,封装子系统的调用逻辑:
class Facede // 注:代码中类名应为Facade(拼写小失误)
{ 
public:Facede() {// 内部初始化所有子系统pSystem1 = new SubSystem1;pSystem2 = new SubSystem2;pSystem3 = new SubSystem3;pSystem4 = new SubSystem4;}void runSystem() {// 统一调用所有子系统的功能pSystem1->run();pSystem2->run();pSystem3->run();pSystem4->run();}
private:// 持有子系统对象(封装细节)SubSystem1* pSystem1;SubSystem2* pSystem2;SubSystem3* pSystem3;SubSystem4* pSystem4;
};

(3)客户端调用

只需与外观类交互,无需关注子系统:

void test01() 
{Facede* facede = new Facede;facede->runSystem(); // 一句话启动所有子系统
}

(4)完整代码

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;/*
外观模式就是将复杂的子类系统抽象到同一个的接口进行管理
,外界只需要通过此接口与子类系统进行交互,而不必要直接与复杂的子类
系统进行交互
*///子系统1
class SubSystem1
{
public:void run(){ cout << "子系统一运行..." << endl; }
};
//子系统2
class SubSystem2
{
public:void run(){ cout << "子系统二运行..." << endl; }
};
//子系统3
class SubSystem3
{
public:void run(){ cout << "子系统三运行..." << endl; }
};
//子系统4
class SubSystem4
{
public:void run(){  cout << "子系统四运行..." << endl;  }
};//外观类
class Facede
{
public:Facede(){pSystem1 = new SubSystem1;pSystem2 = new SubSystem2;pSystem3 = new SubSystem3;pSystem4 = new SubSystem4;}void runSystem(){pSystem1->run();pSystem2->run();pSystem3->run();pSystem4->run();}private:SubSystem1* pSystem1;SubSystem2* pSystem2;SubSystem3* pSystem3;SubSystem4* pSystem4;
};void test01()
{Facede* facede = new Facede;facede->runSystem();
}int main()
{test01();system("pause");return EXIT_SUCCESS;
}

4.3.2外观模式案例

根据类图,实现家庭影院外观模式应用。

实现KTV模式:电视打开,灯关掉,音响打开,麦克风打开,dvd打开;

实现游戏模式:电视打开,音响打开,游戏机打开。

(1)外观模式核心概念

①定位

外观模式是 23 种经典设计模式之一,是简化复杂子系统交互的核心模式。

②核心定义

为一组具有类似功能的类群(如类库、子系统)提供一个一致、简单的统一接口(外观类);将复杂的子系统抽象到同一个接口进行管理,外界仅需通过该外观类与子系统交互,无需关心子系统内部的具体实现。

③设计思路
  • 子系统层:包含多个独立的功能类,实现具体业务逻辑(如电视、灯、音响等);
  • 外观类层:封装子系统的调用逻辑,创建子系统对象并提供统一的调用方法(如 KTV 模式的开启 / 关闭);
  • 客户端层:仅与外观类交互,调用外观类的方法即可完成复杂子系统的协同操作,无需直接操作子系统类。

(2)实战案例:家庭影院外观模式实现

①案例需求

实现家庭影院的核心模式(课程重点实现 KTV 模式,预留游戏模式):

  • KTV 模式逻辑:打开电视、关闭灯、打开音响、打开麦克风、打开 DVD 播放器;
  • 无外观模式的问题:客户端需逐个创建子系统对象,逐个调用开启 / 关闭方法,代码冗余、耦合度高,且需了解所有子系统细节。
②C++ 代码实现解析
A.子系统类:独立功能组件

定义多个子系统类,每个类封装自身的核心行为(开启 / 关闭):

// 电视机
class Television 
{
public:void On() { cout << "电视机打开..." << endl; }void Off() { cout << "电视机关闭..." << endl; }
};// 灯
class Light
{
public:void On() { cout << "灯打开..." << endl; }void Off() { cout << "灯关闭..." << endl; }
};// 音箱、麦克风、DVD、游戏机等子系统类(结构同上)
class Audio { /* On/Off */ };
class Mircophone { /* On/Off */ };
class DVDPlayer { /* On/Off */ };
class Gamemachine { /* On/Off */ };
B.外观类:KTVMode(封装子系统逻辑)

核心作用:将 KTV 模式的复杂操作封装为简单接口,客户端仅需调用该类方法:

class KTVMode 
{
public:// 构造函数:创建所有子系统对象(客户端无需关心)KTVMode() {pTv = new Television;pLight = new Light;pAudio = new Audio;pMicrophone = new Mircophone;pDvd = new DVDPlayer;}// 封装KTV开启逻辑:一键调用所有子系统的对应方法void OnKTV() {pTv->On();pLight->Off();pAudio->On();pMicrophone->On();pDvd->On();}// 封装KTV关闭逻辑void OffKTV(){pTv->Off();pLight->On();pAudio->Off();pMicrophone->Off();pDvd->Off();}// 析构函数:释放子系统对象,避免内存泄漏~KTVMode() {delete pTv;delete pLight;delete pAudio;delete pMicrophone;delete pDvd;}private: // 隐藏子系统对象,避免客户端直接操作Television* pTv;Light* pLight;Audio* pAudio;Mircophone* pMicrophone;DVDPlayer* pDvd;
};
C.客户端调用:极简交互
void test01() 
{// 仅需创建外观类对象,无需关心子系统细节KTVMode* ktv = new KTVMode;// 一键开启KTV模式(底层自动调用所有子系统方法)ktv->OnKTV();
}int main(void) 
{test01();system("pause");return 0;
}

(3)外观模式的核心价值

  1. 降低耦合:客户端与子系统解耦,仅依赖外观类,符合迪米特法则;
  2. 简化调用:将复杂的子系统协同操作封装为外观类的简单方法,客户端一行代码即可完成复杂逻辑;
  3. 模块清晰:子系统各司其职(如 A 开发电视、B 开发音响),通过外观类统一对外提供接口,项目架构更清晰;
  4. 易于维护:子系统逻辑变更时,仅需修改外观类的封装逻辑,客户端代码无需改动,符合 “开闭原则”;
  5. 分工协作友好:多开发者开发不同子系统时,仅需对外暴露外观类接口,避免类之间直接交互导致的混乱。

(4)关键补充

  1. 外观模式中的 “一致简单页面”:本质是外观类提供的统一接口,客户端只需和这个 “简单页面” 交互,无需接触背后的复杂子系统;
  2. 扩展场景(游戏模式):可参考 KTVMode 实现GameMode外观类,封装 “打开游戏机、打开电视、关闭灯、关闭麦克风” 等逻辑;
  3. 设计思想本质:“封装复杂,暴露简单”,让软件模块分工更清晰,整体更易维护。

4.3.3外观模式的优缺点

优点:

  • 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
  • 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
  • 一个子系统的修改对其他子系统没有任何影响。

缺点:

  • 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。
  • 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。

4.3.4适用场景

  • 复杂系统需要简单入口使用。
  • 客户端程序与多个子系统之间存在很大的依赖性。
  • 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

4.4适配器模式

将一个类的接口转换成客户希望的另外一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

  • 手机充电需求:需要 5V 低压 供电
  • 民用供电现状:提供 220V 高压
  • 解决方案:通过 电源适配器 将 220V 转换为 5V,解决 “电压接口不兼容” 问题

类比到编程中:适配器模式就是解决 “现有接口与目标接口不兼容” 的 “编程电源适配器”。

4.4.1适配器模式中的角色和职责

Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。

Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。

Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

  根据对象适配器模式结构图,在对象适配器中,客户端需要调用request()方法,而适配者类Adaptee没有该方法,但是它所提供的specificRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者specificRequest()方法。因为适配器类与适配者类是关联关系(也可称之为委派关系),所以这种适配器模式称为对象适配器模式。

(1)适配器模式的核心定义

已存在的类接口,转换为客户期望的目标接口,使得原本因接口不匹配而无法协同工作的类,能够正常配合使用。

核心目标:解决 “接口不兼容” 问题,复用已有代码,避免修改原有逻辑。

(2)问题场景:为什么需要适配器?

讲课中以for_each算法与自定义函数对象的冲突为例,明确问题根源:

  1. 现有资源:已实现双参数函数对象MyPrint(需传入v1和v2,功能是打印两数之和)
  2. 目标需求:for_each算法遍历vector时,要求传入的函数对象只能接收1 个参数(即vector中的每个元素)
  3. 冲突点:MyPrint的双参数接口与for_each要求的单参数接口不兼容,直接调用会编译报错。

(3)代码实现:分步解析适配器如何工作

  1. 代码整体结构

    #define _CRT_SECURE_NO_WARNINGS
    #include 
    #include#include namespace std;// 1. 已存在的双参数函数对象(待适配的接口)
    struct MyPrint{...};// 2. 目标接口(客户期望的接口)
    class Target{...};// 3. 适配器(连接待适配接口与目标接口)
    class Adapter : public Target{...};// 4. 辅助函数(简化适配器使用)
    Adapter MyBind2nd(int v){...};// 5. 主函数(测试逻辑)
    int main(void){...};
    
  2. 各模块详细解析​

    • 待适配的现有接口:MyPrint

      //这函数我已经写好
      struct MyPrint
      {void operator()(int v1, int v2) { cout << v1 + v2 << endl; }
      };
      
      • 作用:已实现 “两数相加打印” 的核心功能,但接口参数数量不符合for_each需求。

      • 特点:结构体实现函数对象(重载()),无需显式写public(结构体默认 public)。

    • 目标接口:Target(抽象基类)

      class Target
      {
      public:virtual void operator()(int v) = 0;  // 单参数纯虚函数:客户期望的接口
      };
      
      • 作用:定义for_each所需的 “单参数函数对象” 标准,作为适配器的继承目标。

      • 特点:纯虚函数强制子类实现该接口,保证适配器符合目标标准。

    • 核心:适配器类Adapter

      class Adapter : public Target// 1. 继承Target,满足目标接口要求
      {  
      public:// 2. 构造函数:接收“待绑定的第二个参数”(解决参数写死问题)Adapter(int param){this->param = param;  // 保存参数,供后续调用使用}// 3. 实现目标接口:重载单参数operator()virtual void operator()(int v){print(v, param);  // 4. 调用待适配接口:将单参数v与保存的param组合为双参数}public:MyPrint print;  // 组合待适配的MyPrint对象(复用其功能)int param;      // 保存“第二个参数”(动态绑定,避免写死)
      };
      
      • 适配逻辑:

        当for_each调用Adapter的operator()(int v)时(传入vector元素 v),适配器会将v与预存的param一起传给MyPrint的双参数接口,完成 “单参数→双参数” 的转换。

    • 辅助函数:MyBind2nd

      Adapter MyBind2nd(int v)
      {return Adapter(v);  // 工厂函数:创建并返回Adapter对象
      }
      
      • 作用:简化适配器的创建过程,无需在main中显式new Adapter,直接调用MyBind2nd(参数)即可获取适配对象。

      • 对应讲课优化:将最初 “写死的第二个参数(如 100)” 改为 “动态传入”,提升灵活性。

    • 主函数:测试逻辑与运行结果

      int main(void)
      {// 1. 准备数据:vector中存入0~9vector> v; for (int i = 0; i ;i++){v.push_back(i);}// 2. 调用for_each:传入适配器(绑定第二个参数为10)for_each(v.begin(), v.end(), MyBind2nd(10));return 0;
      }
      
      • 运行结果:打印10(0+10)、11(1+10)、...、19(9+10)

      • 关键:for_each只需关注 “单参数接口”,无需知道MyPrint的双参数逻辑,适配器完成了底层转换。

完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;//适配器模式 就是将已经写好的接口,但是这个接口不符合需求
//将写好的接口转换成目标接口//这函数我已经写好
struct MyPrint
{void operator()(int v1, int v2) { cout << v1 + v2 << endl; }
};//定义目标接口 我要是配偶 适配成什么样的
class Target
{
public:virtual void operator()(int v) = 0;
};//写适配器
class Adapter : public Target
{
public:Adapter(int param) { this->param = param; }virtual void operator()(int v) { print(v, param); }
public:MyPrint print;int param;
};//MyBind2nd
Adapter MyBind2nd(int v)
{return Adapter(v);
}int main(void)
{vector<int> v;for (int i = 0; i < 10; i++){v.push_back(i);}for_each(v.begin(), v.end(), MyBind2nd(10));return 0;
}

(4)适配器模式的核心逻辑总结

1.三层结构

  • 目标接口(Target)→ 适配器(Adapter)→ 现有接口(MyPrint)
  • 适配器是 “中间桥梁”,同时满足 “目标接口要求” 和 “复用现有功能”。

2.适配本质

  • 不是修改现有接口或目标接口,而是通过适配器的 “接口转换”,让两者协同工作(符合 “开闭原则”:对修改关闭,对扩展开放)。

(5)适配器模式的优势

  1. 复用现有代码:无需重新编写MyPrint的功能,直接通过适配器复用。
  2. 解耦目标与现有接口:for_each(目标)无需关心MyPrint(现有)的实现,后续修改MyPrint只需调整适配器,不影响目标逻辑。
  3. 灵活性高:通过MyBind2nd可动态绑定第二个参数(如 10、20 等),无需修改适配器核心代码。

(6)扩展思考:当前代码的优化方向

讲课中提到 “当前代码未用模板,通用性不足”:

  • 局限性:当前Adapter仅适配MyPrint(双参数 int 求和),若需适配其他类型(如 string 拼接、float 相乘),需重新写适配器。

  • 优化方案:基于 C++ 模板实现 “通用适配器”,让适配器支持任意类型的函数对象和参数,类似 STL 中的bind2nd(讲课提到可参考 STL 源码思路)。

4.4.2适配器模式的案

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<list>
using namespace std;//A需要治疗感冒
class PersonA
{
public:void treatGanmao(){cout << "A需要治疗感冒!" << endl;}
};//B需要治疗头疼
class PersonB
{
public:void treatTouteng(){cout << "B需要治疗头疼!" << endl;}
};//C需要治疗痔疮
class PersonC
{
public:void treatZhichuang(){cout << "C需要治疗痔疮!" << endl;}
};//目标接口
class Target{
public:virtual void treat() = 0;
};//将PersonA的treatGanmao接口适配成treat
class AdapterPersonA : public Target
{
public:AdapterPersonA(){pPerson = new PersonA;}virtual void treat(){pPerson->treatGanmao();}
private:PersonA* pPerson;
};//将PersonB的treatTouteng接口适配成treat
class AdapterPersonB : public Target
{
public:AdapterPersonB(){pPerson = new PersonB;}virtual void treat(){pPerson->treatTouteng();}
private:PersonB* pPerson;
};//将PersonC的treatZhichuang接口适配成treat
class AdapterPersonC : public Target
{
public:AdapterPersonC(){pPerson = new PersonC;}virtual void treat(){pPerson->treatZhichuang();}
private:PersonC* pPerson;
};//医生
class Doctor
{
public:void addPatient(Target* patient){m_list.push_back(patient);}void startTreat(){for (list<Target*>::iterator it = m_list.begin(); it != m_list.end();it ++){(*it)->treat();}}
private:list<Target*> m_list;
};//测试
void test01()
{//创建三个病人Target* patient1 = new AdapterPersonA;Target* patient2 = new AdapterPersonB;Target* patient3 = new AdapterPersonC;//创建医生Doctor* doctor = new Doctor;doctor->addPatient(patient1);doctor->addPatient(patient2);doctor->addPatient(patient3);//医生逐个对病人进行治疗doctor->startTreat();
}int main()
{test01();system("pause");return EXIT_SUCCESS;
}

4.4.3适配器模式优缺点

优点:

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。
  • 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  • 灵活性和扩展性都非常好,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

缺点:

  适配器中置换适配者类的某些方法比较麻烦。

4.4.4适应场景

  • 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。
  • 想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

参考资料来源:黑马程序员

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询