适配器模式(结构型)
前言
有些小伙伴喜欢去像美国、加拿大,包括中国台湾等国家或地区旅游时候,准备不充分的时候出现这样尴尬的情况:当手机没电的时候,想用自己从国内带的充电器充电时候发现充电器罢工不能用了,这是因为上述这些国家的插座使用的不是国内50Hz的220V电压,而是60Hz的110V电压供电的,电压低了1倍,自然是不能用了。有经验的童鞋会准备一个电源插座转换器,将110V电压转为国内标准的220V电压。这里所说的转换器,其实就是起到一个适配电压的目的,也就是本篇要讨论的适配器模式。
适配器模式
适配器模式,就是将原本接口不兼容的两个类之间通过一个适配器来使被适配的类能被目标类所使用,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器就是在不兼容的东西之间搭建一座桥梁,让二者能很好的兼容在一起工作,分为两种形式:
- 类适配器
类适配器使用多重继承对一个接口与另一个接口进行匹配
- 对象适配器
对象适配器使用对象组合对一个接口与另一个接口进行匹配
意图
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
参与者
Target
定义Client使用的与特定领域相关的接口Adaptee
定义一个已经存在的接口,这个接口需要适配Adapter
对Adaptee的接口与Target的接口进行适配Client
与符合Target接口的对象协同
模式结构
代码实现
类适配器
首先定义一个要适配的目标类
Target
,及接口Request()
:1
2
3
4
5
6
7
8class Target
{
public:
virtual void Request()
{
cout << "Target Request" << endl;
}
};定义一个要被适配的类
Adaptee
,及其接口SpecialRequest()
:1
2
3
4
5
6
7
8
9class Adaptee
{
public:
void SpecialRequest()
{
cout << "Adaptee Special Request" << endl;
}
};定义适配器类
Adapter
,通过多重继承来适配两者,通过调用adaptee
类的Request()
接口OverrideRequest()
接口的方式来进行适配:1
2
3
4
5
6
7
8
9
10class Adapter: public Target, public Adaptee
{
public:
void Request() // Override Target's Request()
{
SpecialRequest();
cout << "Adapter Request" << endl;
}
};测试类适配器模式:
1
2
3
4
5
6
7
8void PrototypeTest()
{
// Class Adapter Test
Target *pT = new Adapter();
pT->Request();
delete pT; pT = NULL;
};
运行结果:
Adaptee Special Request
Adapter Request
对象适配器
首先定义一个目标类
Target_Obj
,同样实现其Request()
接口:1
2
3
4
5
6
7
8
9
10
11
12class Target_Obj
{
public:
Target_Obj() {};
virtual ~Target_Obj() {};
public:
virtual void Request()
{
cout << "Target_Obj Request" << endl;
}
};然后定义一个被适配的类
Adaptee_Obj
,同样提供一个SpecialRequest()
接口:1
2
3
4
5
6
7
8
9class Adaptee_Obj
{
public:
void SpecialRequest()
{
cout << "Adaptee_Obj Special Request" << endl;
}
};最后定义一个适配器类,此类仅继承目标类,被适配器类是通过对象组合的方式来进行适配的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Adapter_Obj: public Target_Obj
{
private:
Adaptee_Obj *m_pAdaptee; // 对象组合方式来适配
public:
Adapter_Obj()
{
m_pAdaptee = new Adaptee_Obj();
}
virtual ~Adapter_Obj()
{
if (NULL != m_pAdaptee)
{
delete m_pAdaptee;
m_pAdaptee = NULL;
}
}
public:
void Request()
{
if(NULL == m_pAdaptee)
{
cout << "new Adaptee Failed!" << endl;
return;
}
m_pAdaptee->SpecialRequest();//调用被适配的对象方法
cout << "Adapter_Obj Request" << endl;
}
};测试对象适配器模式:
1
2
3
4
5
6
7
8void AdapterTest_General()
{
// Object Adapter Test
Target_Obj *pTO = new Adapter_Obj();
pTO->Request();
delete pTO; pTO = NULL;
}
运行结果:
Adaptee_Obj Special Request
Adapter_Obj Request
使用场景
- 使用一个已经存在的类,如果它的接口和你实际要求的不一致时,可以考虑使用适配器模式
- 要在调用者和功能提供者双方都不太容易修改的时候再使用适配器模式,而不是一有不同时就使用它
优缺点
- 优点
- 降低了去实现一个功能点的难度,可以对现有的类进行包装,就可以进行使用了
- 目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用
- 提高了项目质量,现有的类一般都是经过测试的,使用了适配器模式之后,不需要对旧的类进行全面的覆盖测试
- 对象适配器还有如下优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配
- 缺点
- 类适配器使用多重继承,对于被继承的多个父类如果有共同的基类,会出现歧义(虽然可以通过虚拟继承来解决,但是使系统的类继承体系膨胀)
- 对象适配器模式与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,其将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂
适配器模式具体实例
实例1 — STL标准库
使用STL的双端队列deque类中的方法实现STL的队列queue & 栈stack类,并实现其中的push和pop方法
被适配的类
Deque
:1
2
3
4
5
6
7
8
9//双端队列
class Deque
{
public:
void push_back(int x) { cout<<"Deque push_back"<<endl; }
void push_front(int x) { cout<<"Deque push_front"<<endl; }
void pop_back() { cout<<"Deque pop_back"<<endl; }
void pop_front() { cout<<"Deque pop_front"<<endl; }
};Queue
和Stack
均属于Sequence
类型(适配器的目标类型):1
2
3
4
5
6
7//顺序容器
class Sequence
{
public:
virtual void push(int x) = 0;
virtual void pop() = 0;
};将
Deque
分别适配为Queue
和Stack
类,并实现push()
和pop()
方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36//栈:先进后出FILO
class Stack: public Sequence
{
public:
void push(int x)
{
deque.push_back(x);
cout << "Stack push" << endl;
}
void pop()
{
deque.pop_back();
cout << "Stack pop" << endl;
}
private:
Deque deque; //双端队列
};
//队列:先进先出FIFO
class Queue: public Sequence
{
public:
void push(int x)
{
deque.push_back(x);
cout << "Queue push" << endl;
}
void pop()
{
deque.pop_front();
cout << "Queue pop" << endl;
}
private:
Deque deque; //双端队列
};测试STL适配器模式:
1
2
3
4
5
6
7
8
9void AdapterTest_STL()
{
// STL Sequence Obj Test
Sequence *s1 = new Stack();
Sequence *s2 = new Queue();
s1->push(1); s1->pop();
s2->push(1); s2->pop();
delete s1; delete s2;
}测试结果:
Deque push_back
Stack push
Deque pop_back
Stack pop
Deque push_back
Queue push
Deque pop_front
Queue pop
实例2 — 电源适配器
实现前言所描述的电源适配器
代码实现
被适配的美国类型插座类
USASocket
:1
2
3
4
5
6
7
8
9
10// Adaptee socket
class USASocket
{
// USA socket;
public:
int provideUSAVolt(void)
{
return 110;
}
};目标类中国类型插座类
CHNSocket
:1
2
3
4
5
6
7// Target socket
class CHNSocket
{
// China socket;
public:
virtual int provideChinaVolt(void) = 0;
};美国–>中国插座转换器类
SocketAdapter
(采用对象适配器模式):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// socket Adapter
class SocketAdapter : public CHNSocket
{
private:
USASocket* _usaSocket;
public:
SocketAdapter(USASocket *socket):_usaSocket(socket) {};
public:
int provideChinaVolt(void)
{
if (NULL != _usaSocket)
{
return _usaSocket->provideUSAVolt() * 2;
// invoke USASocket method to convert usa volt ---> chn volt
}
return 0;
}
};测试实例:
1
2
3
4
5
6
7
8
9
10void AdapterTest_Socket()
{
// Socket Adapter Test:
USASocket *s1 = new USASocket();
SocketAdapter *s2 = new SocketAdapter(s1);
cout << "before convert volt is: " << s1->provideUSAVolt() << endl;
cout << "after convert volt is: " << s2->provideChinaVolt() << endl;
delete s1; delete s2;
}
运行结果:
before convert volt is: 110
after convert volt is: 220