目录
一.专栏简介
本专栏是我学习《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类,采用随机数来实现飞行方法平均五次飞一次。
运行结果:

五.适配器模式解析

客户使用适配器的做法如下:
- 客户通过使用目标接口,调用适配器的方法,对适配器做出请求。
- 适配器使用被适配者接口,把请求翻译成被适配者上的一个或多个调用。
- 客户收到调用结果,但根本不知道是适配器在做翻译。
六.定义适配器模式
适配器模式的官方定义:
适配器模式将一个类的接口转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作。
类图如下:

适配器模式里充满了良好的面向对象设计原则:我们可以看到对象组合的使用,用修改的接口来包装被适配者。这个做法还有额外的优点,我们可以使用适配器搭配被适配者的任何子类。
七.对象和类适配器
实际上有两种适配器模式:对象适配器和类适配器。上面说的就是对象适配器。
实现类适配器需要多重继承,它的类图为:

与对象适配器比较像。类适配器继承了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库中,我们经常看到迭代器的声影,迭代器就是一种适配器,它转换了指针接口的行为,让我们像使用指针一样去操作数据结构。
在下一篇博客中,我们会学习外观模式,我们先对外观模式,适配器模式,装饰者模式做一个比较,因为它们比较相似。
- 装饰者:不改变接口,但添加责任。
- 适配器:转换一个接口为另一个。
- 外观:使接口更简单。