3、结构型模式
约 3783 字大约 13 分钟
2025-06-22
将一个类的接口转换成用户希望的另一个接口,使不兼容的对象能够相互配合并一起工作,这种模式就叫适配器模式。 适配器模式就相当于找了一个翻译。
STL标准模板库有六大组件,其中之一的就是适配器。 六大组件分别是:容器、算法、迭代器、仿函数、适配器、空间适配器。 适配器又可以分为:容器适配器、函数适配器、迭代器适配器
斜杠型人才 所谓的斜杠型人才就是多才多艺,适配器也一样,如果它能给多个不相干的对象进行相互之间的适配,这个适配器就是斜杠适配器。
其中设配器类可以关联要翻译的类也可以继承要翻译的类(多继承),能够简化少许代码 C++是支持多继承的
#include <iostream>
using namespace std;
// 适配器模式
/**
* 外国人
*/
class People {
public:
virtual string speak() = 0;
void listen(string msg) { cout << "我听到: " << msg << endl; }
virtual ~People() {}
};
/**
* 美国人
*/
class American : public People {
public:
string speak() override { return "fuck"; }
};
/**
* 中国人
*/
class Chinese : public People {
public:
string speak() override { return "八嘎"; }
};
// 抽象翻译适配器
class AbstractTranslator {
protected:
People* speaker1;
People* speaker2;
public:
AbstractTranslator(People* p1, People* p2) : speaker1(p1), speaker2(p2) {}
virtual void listens2() = 0;
virtual void saytos2() = 0;
virtual ~AbstractTranslator() {}
};
// 中文翻译适配器
class ChineseTranslator : public AbstractTranslator {
public:
using AbstractTranslator::AbstractTranslator;
void listens2() override {
string msg = "牢美说:" + speaker2->speak();
speaker1->listen(msg);
}
void saytos2() override {
string msg = "给牢美说:" + speaker1->speak();
speaker2->listen(msg);
}
};
// 英文翻译适配器
class AmericanTranslator : public AbstractTranslator {
public:
using AbstractTranslator::AbstractTranslator;
void listens2() override {
string msg = "Chinese say:" + speaker2->speak();
speaker1->listen(msg);
}
void saytos2() override {
string msg = "say to Chinese:" + speaker1->speak();
speaker2->listen(msg);
}
};
void test() {
People* chinese = new Chinese();
People* american = new American();
AbstractTranslator* chineseTranslator =
new ChineseTranslator(chinese, american);
chineseTranslator->listens2();
chineseTranslator->saytos2();
AbstractTranslator* americanTranslator =
new AmericanTranslator(american, chinese);
americanTranslator->listens2();
americanTranslator->saytos2();
delete chinese;
delete american;
delete chineseTranslator;
delete americanTranslator;
}
桥接模式
方案一
因为每个船员都具有所属海贼团的一些特性,所以可以让每个船员都从对应的海贼团类派生,如下图,它描述的某一个海贼团中类和类之间的关系:
方案二
将海贼团的船员和海贼团之间的继承关系,改为聚合关系,如下图: 上面的两种方案你觉得哪个更合理一些呢?很明显,是第二种。
第一种解决方案
- 每个船员都是当前海贼团的子类,这样船员就继承了海贼团的属性,合情合理。
- 如果当前海贼团添加了一个成员就需要给当前海贼团类添加一个子类。拿路飞举个例子,他在德雷斯罗萨王国打败多弗朗明哥之后,组成了草帽大船团,小弟一下子扩充了5640人,难道要给草帽团添加五千多个子类吗?如果这样处理,海贼船和船员的耦合度就比较高了。
第二种解决方案
- 海贼团之间是继承关系,但是此时的海贼团也只是一个抽象,因为组成海贼团的人已经被抽离了,船员已经和所属的海贼团没有了继承关系。
- 关于海贼世界的船员在船上对应不同的职责担任不同的职务,他们是一个团队,所以可以给船员抽象出一个团队类,用于管理船上的成员。
- 抽象的海贼团只有一个空壳子,所以要赋予其灵魂也就是给它添加船员,此时的海贼团和船员团队可以通过聚合的方式组合成为一个整体。
- 这种解决方案不仅适用于管理海贼团,用于管理海军的各个舰队也是没有问题的。
使用第二种解决方案程序猿的工作量会更少一些,且更容易维护和扩展。第二种方式的原则就是将抽象部分和它的实现部分分离,使它们可以独立的变化,这种处理模式就是桥接模式。
#include <iostream>
#include <map>
using namespace std;
// 桥接模式
// 团队
class AbstractTeam {
public:
AbstractTeam(string name) : name(name) {}
string getTeamName() { return name; }
void addMember(string job, string name) {
m_info.insert(pair<string, string>(job, name));
}
void show() {
cout << "团队名称:" << name << endl;
for (map<string, string>::iterator it = m_info.begin(); it != m_info.end();
it++) {
cout << it->first << ":" << it->second << endl;
}
cout << endl;
}
virtual void Task() = 0;
protected:
string name;
map<string, string> m_info;
};
// 草帽海贼团
class SeaTribe : public AbstractTeam {
public:
using AbstractTeam::AbstractTeam;
void Task() { cout << "成为海贼王" << endl; }
};
// 斯摩格海军
class Navy : public AbstractTeam {
using AbstractTeam::AbstractTeam;
void Task() { cout << "维护和平" << endl; }
};
// 船
class AbstractShip {
protected:
AbstractTeam *team;
public:
AbstractShip(AbstractTeam *team) : team(team) {}
void show() {
team->show();
team->Task();
}
virtual string getName() = 0;
virtual void feature() = 0;
virtual ~AbstractShip() {}
};
class Merry : public AbstractShip {
public:
using AbstractShip::AbstractShip;
string getName() { return "前进!梅丽号"; }
void feature() { cout << getName() << "梅丽号是草帽海贼团的船" << endl; }
};
class Marine : public AbstractShip {
public:
using AbstractShip::AbstractShip;
string getName() { return "前进!海军号"; }
void feature() { cout << getName() << "海军号是斯摩格海军的船" << endl; }
};
void test() {
SeaTribe seaTribe("草帽海贼团");
seaTribe.addMember("船长", "路飞");
seaTribe.addMember("船医", "乔巴");
seaTribe.addMember("航海士", "娜美");
Merry merry(&seaTribe);
merry.feature();
merry.show();
Navy navy("斯摩格海军");
navy.addMember("上将", "赤犬");
navy.addMember("中将", "绿牛");
navy.addMember("少将", "黄猿");
Marine marine(&navy);
marine.feature();
marine.show();
}
组合模式
能将多个对象组成一个树状结构,用以描述部分—整体的层次关系,使得用户对单个对象和组合对象的使用具有一致性,这样的结构性设计模式叫做组合模式。
#include <iostream>
#include <list>
using namespace std;
// 组合模式
// 抽象节点类
class AbstractNode {
public:
bool hasChild = false;
string name;
AbstractNode* parent;
AbstractNode(string name) : name(name) {}
virtual void add(AbstractNode* node) {}
virtual void remove(AbstractNode* node) {}
virtual void fight() = 0;
virtual void show() = 0;
virtual ~AbstractNode() {}
};
// 叶子节点类
class LeafNode : public AbstractNode {
public:
using AbstractNode::AbstractNode;
void fight() override { cout << name << "战斗" << endl; }
void show() override { cout << parent->name << " " << name << endl; }
~LeafNode() override { cout << "delete " << name << endl; }
};
// 非叶子节点类
class CompositeNode : public AbstractNode {
private:
list<AbstractNode*> children;
public:
using AbstractNode::AbstractNode;
void add(AbstractNode* node) override {
children.push_back(node);
node->parent = this;
hasChild = true;
}
void fight() override { cout << name << "爷们要战斗" << endl; }
void remove(AbstractNode* node) override {
children.remove(node);
node->parent = nullptr;
if (children.size() == 0) {
hasChild = false;
}
}
void show() override {
cout << name << "的队伍" << endl;
for (auto& child : children) {
child->show();
}
cout << endl;
}
~CompositeNode() override {
cout << "delete- " << name << endl;
}
};
void test(){
CompositeNode* root = new CompositeNode("root");
CompositeNode* node1 = new CompositeNode("node1");
CompositeNode* node2 = new CompositeNode("node2");
CompositeNode* node3 = new CompositeNode("node3");
LeafNode* leaf1 = new LeafNode("leaf1");
LeafNode* leaf2 = new LeafNode("leaf2");
LeafNode* leaf3= new LeafNode("leaf3");
LeafNode* leaf4 = new LeafNode("leaf4");
LeafNode* leaf5 = new LeafNode("leaf5");
LeafNode* leaf6 = new LeafNode("leaf6");
root->add(node1);
root->add(node2);
root->add(node3);
node1->add(leaf1);
node1->add(leaf2);
node2->add(leaf3);
node2->add(leaf4);
node3->add(leaf5);
node3->add(leaf6);
root->show();
delete root;
delete node1;
delete node2;
delete node3;
delete leaf1;
delete leaf2;
delete leaf3;
delete leaf4;
delete leaf5;
delete leaf6;
}
- 继承关系:对节点的操作使用的是抽象类中提供的接口,以保证操作的一致性
- 聚合关系:ManagerTeam类型的节点还可以有子节点,父节点和子节点的之间的关系需要具体问题具体分析 子节点跟随父节点一起销毁,二者就是组合关系(UML中的组合关系) 子节点不跟随父节点一起销毁,二者就是聚合关系
上面的程序中,在父节点的析构函数中没有销毁它管理的子节点,所以在上图中标记的是聚合关系
装饰模式
装饰模式也可以称之为封装模式,所谓的封装就是在原有行为之上进行拓展,并不会改变该行为
#include <iostream>
using namespace std;
// 装饰模式
// 战士
class Warrior {
public:
virtual void fight() { cout << "出拳" << endl; }
};
// 力道蛊仙
class WarriorDecorator : public Warrior {
public:
void fight() {
Warrior::fight();
cout << "打出力道虚影造成攻击" << endl;
}
};
// 火焰蛊仙
class WarriorDecorator2 : public WarriorDecorator {
void fight() {
WarriorDecorator::fight();
cout << "攻击附带灼烧伤害" << endl;
}
};
void test(){
Warrior *warrior = new Warrior();
warrior->fight();
cout << "====================" << endl;
Warrior *warrior2 = new WarriorDecorator();
warrior2->fight();
cout << "====================" << endl;
Warrior *warrior3 = new WarriorDecorator2();
warrior3->fight();
cout << "====================" << endl;
}
外观模式
外观模式就是给很多复杂的系统封装起来提供一个简单的上层接口,并在这些接口中包含用户真正关心的功能。 因为类之间没有继承关系,也不是整体和部分这种结构,因此排除了聚合和组合,并且它们之间具有包含和被包含的关系,所以确定的关系是关联关系
#include <iostream>
using namespace std;
// 外观模式
// 乐可系统
class CokeSystem {
public:
void immitCoke() { cout << "狮吼炮原料<可乐>已经注入完毕..." << endl; }
};
// 能量转换系统
class EnergySystem {
public:
void energyConvert() { cout << "已经将所有的可乐转换为了能量..." << endl; }
};
// 目标锁定系统
class AimLockSystem {
public:
void aimLock() { cout << "已经瞄准并且锁定了目标..." << endl; }
};
// 加农炮发射系统
class Cannon {
public:
void cannonFire() { cout << "狮吼炮正在向目标开火..." << endl; }
};
// 风来炮稳定系统
class WindCannon {
public:
void windCannonFire() { cout << "发射风来炮抵消后坐力稳定船身..." << endl; }
};
// 上层接口
class LionCannon {
public:
LionCannon() {
m_coke = new CokeSystem;
m_energy = new EnergySystem;
m_aimLock = new AimLockSystem;
m_cannon = new Cannon;
m_windCannon = new WindCannon;
}
~LionCannon() {
delete m_coke;
delete m_energy;
delete m_aimLock;
delete m_cannon;
delete m_windCannon;
}
// 瞄准并锁定目标
void aimAndLock() {
m_coke->immitCoke();
m_energy->energyConvert();
m_aimLock->aimLock();
}
// 开炮
void fire() {
m_cannon->cannonFire();
m_windCannon->windCannonFire();
}
private:
CokeSystem* m_coke = nullptr;
EnergySystem* m_energy = nullptr;
AimLockSystem* m_aimLock = nullptr;
Cannon* m_cannon = nullptr;
WindCannon* m_windCannon = nullptr;
};
void test() {
// 发射狮吼炮
LionCannon* lion = new LionCannon;
lion->aimAndLock();
lion->fire();
delete lion;
}
外观模式是一个很重要、平时也经常使用的设计模式,其核心思想就是化繁为简,封装底层逻辑,将使用者真正关心的功能通过上层接口呈现出来。
享元模式
如果想要实现一个发射炮弹的游戏,有一个很现实的亟待解决的问题:内存的消耗问题。
- 每个炮弹都是一个对象,每个对象都会占用一块内存
- 炮弹越多,占用的内存就越大,如果炮弹足够多可能会出现内存枯竭问题
- 假设内存足够大,频繁的创建炮弹对象,会影响游戏的流畅度,性能低
关于游戏中的炮弹,应该有以下一些需要处理的属性:
- 炮弹的坐标
- 炮弹的速度
- 炮弹的颜色渲染
- 炮弹的精灵图(就是一张大图上有很多小的图片,通过进行位置的控制,从大图中取出想要的某一张小的图片) 在这四部分数据中有些属性是动态的,有些属性是静态的:
- 静态资源:精灵图和渲染的颜色
- 动态属性:坐标和速度 对应的动态资源肯定是不能被复用,所有炮弹可共享的就是这些静态资源,不论有多少炮弹,它们对应的精灵图和渲染颜色数据可以只有一份,这样对于内存的开销就大大降低了。
享元模式就是摒弃了在每个对象中都保存所有的数据的这种方式,通过数据共享(缓存)让有限的内存可以加载更多的对象。
对象的常量数据通常被称为内在状态, 其位于对象中, 其他对象只能读取但不能修改其数值。 而对象的其他状态常常能被其他对象 “从外部” 改变, 因此被称为外在状态。使用享元模式一般建议将内在状态和外在状态分离,将内在状态单独放到一个类中,这种类我们可以将其称之为享元类。
// 享元基类
class FlyweightBody
{
public:
FlyweightBody(string sprite) : m_sprite(sprite) {}
virtual void move(int x, int y, int speed) = 0;
virtual void draw(int x, int y) = 0;
virtual ~FlyweightBody() {}
protected:
string m_sprite; // 精灵图片
string m_color = string("black"); // 渲染颜色
};
// 炸弹弹体
class SharedBombBody : public FlyweightBody
{
public:
using FlyweightBody::FlyweightBody;
void move(int x, int y, int speed) override
{
cout << "炸弹以每小时" << speed << "速度飞到了("
<< x << ", " << y << ") 的位置..." << endl;
}
void draw(int x, int y) override
{
cout << "在 (" << x << ", " << y << ") 的位置重绘炸弹弹体..." << endl;
}
};
// 唯一的炸弹彩蛋
class UniqueBomb : public FlyweightBody
{
public:
using FlyweightBody::FlyweightBody;
void move(int x, int y, int speed) override
{
// 此处省略对参数 x, y, speed的处理
cout << "彩蛋在往指定位置移动, 准备爆炸发放奖励..." << endl;
}
void draw(int x, int y) override
{
cout << "在 (" << x << ", " << y << ") 的位置重绘彩蛋运动轨迹..." << endl;
}
};
// 发射炮弹
class LaunchBomb
{
public:
LaunchBomb(FlyweightBody* body) : m_bomb(body) {}
int getX()
{
return m_x;
}
int getY()
{
return m_y;
}
void setSpeed(int speed)
{
m_speed = speed;
}
int getSpeed()
{
return m_speed;
}
void move(int x, int y)
{
m_x = x;
m_y = y;
m_bomb->move(m_x, m_y, m_speed);
draw();
}
void draw()
{
m_bomb->draw(m_x, m_y);
}
private:
int m_x = 0;
int m_y = 0;
int m_speed = 100;
FlyweightBody* m_bomb = nullptr;
};
// 享元工厂类
class BombBodyFactory
{
public:
SharedBombBody* getSharedData(string name)
{
SharedBombBody* data = nullptr;
// 遍历容器
for (auto item : m_bodyMap)
{
if (item.first == name)
{
// 找到了
data = item.second;
cout << "正在复用 <" << name << ">..." << endl;
break;
}
}
if (data == nullptr)
{
data = new SharedBombBody(name);
cout << "正在创建 <" << name << ">..." << endl;
m_bodyMap.insert(make_pair(name, data));
}
return data;
}
~BombBodyFactory()
{
for (auto item : m_bodyMap)
{
delete item.second;
}
}
private:
map<string, SharedBombBody*> m_bodyMap;
};
int main()
{
// 发射炮弹
BombBodyFactory* factory = new BombBodyFactory;
vector<LaunchBomb*> ballList;
vector<string> namelist = { "撒旦-1", "撒旦-1", "撒旦-2", "撒旦-2", "撒旦-2", "撒旦-3"};
for (auto name : namelist)
{
int x = 0, y = 0;
LaunchBomb* ball = new LaunchBomb(factory->getSharedData(name));
for (int i = 0; i < 3; ++i)
{
x += rand() % 100;
y += rand() % 50;
ball->move(x, y);
}
cout << "=========================" << endl;
ballList.push_back(ball);
}
// 彩蛋
UniqueBomb* unique = new UniqueBomb("大彩蛋");
LaunchBomb* bomb = new LaunchBomb(unique);
int x = 0, y = 0;
for (int i = 0; i < 3; ++i)
{
x += rand() % 100;
y += rand() % 50;
bomb->move(x, y);
}
for (auto ball : ballList)
{
delete ball;
}
delete factory;
delete unique;
delete bomb;
return 0;
}
- 享元模式中的享元类可以有子类也可以没有
- 享元模式中可以添加享元工厂也可以不添加
- 享元工厂的作用和单例模式类似,但二者的关注点略有不同 单例模式关注的是类的对象有且只有一个 享元工厂关注的是某个实例对象是否可以共享
代理模式
为其他对象提供一种代理,以控制对这个对象的访问。 如果我们想要用代理模式来描述一下电话虫的行为,里边有如下几个细节:
- 说话的人是一个对象,电话虫也是一个对象,电话虫模拟的是说话的人
- 说话的人和电话虫有相同的行为,所以需要为二者提供一个抽象类
- 电话虫是在为说话的人办事,所以电话虫和说话人应该有关联关系
#include <iostream>
using namespace std;
// 代理模式
// 抽象通话类
class Communication {
public:
virtual void call() = 0;
virtual ~Communication() {}
};
// 讲话者
class Speaker : public Communication {
public:
void call() { cout << "讲话者正在讲话..." << endl; }
};
// 电话
class Phone : public Communication {
private:
Speaker* speaker;
bool isSpeaker;
public:
Phone() {
this->speaker = new Speaker();
this->isSpeaker = true;
}
~Phone() {
if (this->isSpeaker) {
delete this->speaker;
}
}
void call() { cout << "电话代理讲话者说话" << endl; }
};
void test() {
Communication* phone = new Phone();
phone->call();
delete phone;
}
我们可以在代理类中有效的管理被代理的对象的工作的时机,但是并没有改变被代理的对象的行为。
[!tip] Title 通过测试程序我们可以得到如下结论:如果使用代理模式,不能改变所代理的类的接口,使用代理模式的目的是为了加强控制。
贡献者
版权所有
版权归属:PinkDopeyBug