基于C++的《Head First设计模式》笔记——适配器模式

目录

一.专栏简介

二.我们周围的适配器

三.面向对象适配器

四.鸭子适配器

五.适配器模式解析

六.定义适配器模式

七.对象和类适配器

八.总结


一.专栏简介

本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。

本章将开始适配器和外观模式的学习。

二.我们周围的适配器

适配器在生活中随处可见。比如,我们在美国使用英国接口的电器,就需要一个适配器来美转英。如下图:

这是现实世界,那面向对象适配器呢?嗯,我们的OO适配器扮演和现实世界同样的角色,把一个接口适配成符合客户期望的接口。

三.面向对象适配器

假设已有一个软件系统,你需要把一个新的厂商类库整合进去,但新的厂商设计的接口和之前的厂商不同。

OK,我们不能通过改变已有代码来解决问题(而且不能改变厂商的代码)。我们可以编写一个类,把新的厂商接口适配成我们所期望的接口。

适配器扮演中间人。它接收来自客户的请求,并转换成厂商类能理解的请求。

四.鸭子适配器

如果它走路像鸭子,叫起来像鸭子,那么它一定可能是鸭子被鸭子适配器包装的火鸡~~

我们把策略模式那章的鸭子拿出来"鞭尸",先实现一个简化版本的Duck接口和类:

Adapter.h:

cpp 复制代码
#pragma once

#include <iostream>
using namespace std;

// 鸭子接口(抽象类)
class Duck
{
public:
	virtual void quack() = 0;
	virtual void fly() = 0;
};

// 绿头鸭子类
class MallardDuck : public Duck
{
public:
	void quack() override;
	void fly() override;
};

// 火鸡接口(抽象类)
class Turkey
{
public:
	virtual void gobble() = 0;
	virtual void fly() = 0;
};

// 胖火鸡子类
class WildTurkey : public Turkey
{
public:
	void gobble() override;
	void fly() override;
};

Adapter.cpp:

cpp 复制代码
#include "Adapter.h"

void MallardDuck::quack()
{
	cout << "Quack" << endl;
}

void MallardDuck::fly()
{
	cout << "I'm flying!" << endl;
}

void WildTurkey::gobble()
{
	cout << "Gobble gobble" << endl;
}

void WildTurkey::fly()
{
	cout << "I'm flying a short distance" << endl;
}

现在,假设我们的Duck对象短缺,想用一些Turkey对象来充数。显然我们不能堂而皇之地使用火鸡,因为它们接口不同。

因此,我们来编写一个适配器:

Adapter.h:

cpp 复制代码
class TurkeyAdapter : public Duck
{
public:
	TurkeyAdapter(Turkey* turkey);
	~TurkeyAdapter();
	void quack();
	void fly();
private:
	Turkey* turkey;
};

class DuckAdapter : public Turkey
{
public:
	DuckAdapter(Duck* duck);
	~DuckAdapter();
	void gobble() override;
	void fly() override;
private:
	Duck* duck;
};

Adapter.cpp:

cpp 复制代码
TurkeyAdapter::TurkeyAdapter(Turkey* turkey):
	turkey(turkey)
{
}

TurkeyAdapter::~TurkeyAdapter()
{
	delete turkey;
}

void TurkeyAdapter::quack()
{
	turkey->gobble();
}

void TurkeyAdapter::fly()
{
	for(int i = 0;i < 5;++i)
	{
		turkey->fly();
	}
}

DuckAdapter::DuckAdapter(Duck* duck):
	duck(duck)
{
	srand(time(0));
}

DuckAdapter::~DuckAdapter()
{
	delete duck;
}

void DuckAdapter::gobble()
{
	duck->quack();
}

void DuckAdapter::fly()
{
	if (rand() % 5 == 0)
	{
		duck->fly();
	}
}

即时两个接口都有fly()方法,火鸡的飞行只是短距离冲刺,不能像鸭子那样长途飞行。要在Duck的fly()方法和Turkey的fly()方法之间形成映射,我们需要调用Turkey()的fly()方法五次来补足。

main.cpp测试代码:

cpp 复制代码
#include "Adapter.h"

void testDuck(Duck* duck)
{
	duck->quack();
	duck->fly();
}

int main()
{
	Duck* duck = new MallardDuck();

	Turkey* turkey = new WildTurkey();
	Duck* turkeyAdapter = new TurkeyAdapter(turkey);

	cout << "The turkey says..." << endl;
	turkey->gobble();
	turkey->fly();

	cout << "\nThe duck says..." << endl;
	testDuck(duck);

	cout << "\nThe turkeyAdapter says..." << endl;
	testDuck(turkeyAdapter);

	return 0;
}

上面代码我们还顺便实现了DuckAdapter类,采用随机数来实现飞行方法平均五次飞一次。

运行结果:

五.适配器模式解析

客户使用适配器的做法如下:

  1. 客户通过使用目标接口,调用适配器的方法,对适配器做出请求。
  2. 适配器使用被适配者接口,把请求翻译成被适配者上的一个或多个调用。
  3. 客户收到调用结果,但根本不知道是适配器在做翻译。

六.定义适配器模式

适配器模式的官方定义:

适配器模式将一个类的接口转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作。

类图如下:

适配器模式里充满了良好的面向对象设计原则:我们可以看到对象组合的使用,用修改的接口来包装被适配者。这个做法还有额外的优点,我们可以使用适配器搭配被适配者的任何子类。

七.对象和类适配器

实际上有两种适配器模式:对象适配器和类适配器。上面说的就是对象适配器。

实现类适配器需要多重继承,它的类图为:

与对象适配器比较像。类适配器继承了Target和Adaptee,而对象适配器用组合把请求传递给Adaptee。对象适配器和类适配器使用两种不同的适配方式(组合与继承),正如前面博客笔记说的,组合优于继承,组合时我们松耦合,更易扩展,能够改变行为,更容易遵循单一职责原则

添加类适配器之后的代码和运行结果:

Adapter.h:

cpp 复制代码
#pragma once

#include <iostream>
#include <ctime>
using namespace std;

// 鸭子接口(抽象类)
class Duck
{
public:
	virtual void quack() = 0;
	virtual void fly() = 0;
};

// 绿头鸭子类
class MallardDuck : public Duck
{
public:
	void quack() override;
	void fly() override;
};

// 火鸡接口(抽象类)
class Turkey
{
public:
	virtual void gobble() = 0;
	virtual void fly() = 0;
};

// 胖火鸡子类
class WildTurkey : public Turkey
{
public:
	void gobble() override;
	void fly() override;
};

// 火鸡适配为鸭子的适配器
class TurkeyAdapter : public Duck
{
public:
	TurkeyAdapter(Turkey* turkey);
	~TurkeyAdapter();
	void quack() override;
	void fly() override;
private:
	Turkey* turkey;
};

class ClassTurkeyAdapter : public Duck, public WildTurkey
{
public:
	void quack() override;
	void fly() override;
};

class DuckAdapter : public Turkey
{
public:
	DuckAdapter(Duck* duck);
	~DuckAdapter();
	void gobble() override;
	void fly() override;
private:
	Duck* duck;
};

Adapter.cpp:

cpp 复制代码
#include "Adapter.h"

void MallardDuck::quack()
{
	cout << "Quack" << endl;
}

void MallardDuck::fly()
{
	cout << "I'm flying!" << endl;
}

void WildTurkey::gobble()
{
	cout << "Gobble gobble" << endl;
}

void WildTurkey::fly()
{
	cout << "I'm flying a short distance" << endl;
}

TurkeyAdapter::TurkeyAdapter(Turkey* turkey):
	turkey(turkey)
{
}

TurkeyAdapter::~TurkeyAdapter()
{
	delete turkey;
}

void TurkeyAdapter::quack()
{
	turkey->gobble();
}

void TurkeyAdapter::fly()
{
	for(int i = 0;i < 5;++i)
	{
		turkey->fly();
	}
}

DuckAdapter::DuckAdapter(Duck* duck):
	duck(duck)
{
	srand(time(0));
}

DuckAdapter::~DuckAdapter()
{
	delete duck;
}

void DuckAdapter::gobble()
{
	duck->quack();
}

void DuckAdapter::fly()
{
	if (rand() % 5 == 0)
	{
		duck->fly();
	}
}

void ClassTurkeyAdapter::quack()
{
	gobble();
}

void ClassTurkeyAdapter::fly()
{
	for (int i = 0;i < 5;++i)
	{
		WildTurkey::fly();
	}
}

main.cpp:

cpp 复制代码
#include "Adapter.h"

void testDuck(Duck* duck)
{
	duck->quack();
	duck->fly();
}

int main()
{
	Duck* duck = new MallardDuck();

	Turkey* turkey = new WildTurkey();
	Duck* turkeyAdapter = new TurkeyAdapter(turkey);
	Duck* classTurkeyAdapter = new ClassTurkeyAdapter();

	cout << "The turkey says..." << endl;
	turkey->gobble();
	turkey->fly();

	cout << "\nThe duck says..." << endl;
	testDuck(duck);

	cout << "\nThe turkeyAdapter says..." << endl;
	testDuck(turkeyAdapter);

	cout << "\nClass adapter..." << endl;
	testDuck(classTurkeyAdapter);

	return 0;
}

可以看到,类适配器是继承了一个被适配者的具体类,再继承一个适配者的基类,都是public继承

八.总结

在C++,特别是stl库中,我们经常看到迭代器的声影,迭代器就是一种适配器,它转换了指针接口的行为,让我们像使用指针一样去操作数据结构。

在下一篇博客中,我们会学习外观模式,我们先对外观模式,适配器模式,装饰者模式做一个比较,因为它们比较相似。

  • 装饰者:不改变接口,但添加责任。
  • 适配器:转换一个接口为另一个。
  • 外观:使接口更简单。
相关推荐
学嵌入式的小杨同学2 小时前
循环队列(顺序存储)完整解析与实现(数据结构专栏版)
c语言·开发语言·数据结构·c++·算法
txinyu的博客2 小时前
C++ 单例模式
c++·单例模式
点云SLAM2 小时前
C++ 设计模式之工厂模式(Factory)和面试问题
开发语言·c++·设计模式·面试·c++11·工厂模式
咒法师无翅鱼2 小时前
【西电机器学习】学习笔记(基础部分)
笔记·学习
玖釉-2 小时前
[Vulkan 学习之路] 05 - 缔结契约:创建逻辑设备 (Logical Device)
c++·windows·图形渲染
彩妙不是菜喵2 小时前
c++:初阶/初始模版
开发语言·c++
想唱rap2 小时前
MySQL表得内外连接
服务器·数据库·c++·mysql·ubuntu
A7bert7772 小时前
【DeepSeek R1部署至RK3588】RKLLM转换→板端部署→局域网web浏览
c++·人工智能·深度学习·ubuntu·自然语言处理·nlp
saoys2 小时前
Opencv 学习笔记:精准提取图像中的水平线 / 垂直线(形态学操作实战)
笔记·opencv·学习