设计模式系列(十)适配器模式(Adapter Pattern)

设计模式系列(十)适配器模式(Adapter Pattern)


    适配器模式是将一个类的接口转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。适配器实现了客户与具体实现的解耦,简单来说,适配器模式就是改变接口以符合客户的期望。例如:我们生活中常见的插座的适配器、无线网卡适配器、电源适配器等,这些都是用来进行不同接口的转换,更具体的来说就是我们手机充电的时候可能只需要5V的电压,而插座里面提供的交流电是220V的,那么我们通过一系列的适配器将电压转化为5V以供我们手机充电使用。

    适配器模式中的角色主要有:

(1)目标角色(Target):这个角色就是客户所期望得到的,例如上例中的5V电压就是客户期望得到的目标角色;

(2)被适配角色(Adaptee):这个角色也很好理解,就是需要被适配成目标角色的角色,例如上例中的220V电压;

(3)适配器角色(Adapter):这个角色就是一个中间者,用来将被适配角色转换成目标角色,相当于是一个中间处理的环节。

    适配器模式的使用前提是:接口中规定了所有要实现的方法;一个要实现此接口的具体类可能只用到了其中的几个方法,而其它的方法都是没有用的。需要注意的是,适配器模式在通常的例子中都是只适配一个类,但是这并不意味着适配器模式只能用来适配一个类,当有需求的时候,可以适配多个类,所以大家不要误以为适配器模式只能用来适配一个类。

   适配器主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,在遗留代码复用、类库迁移等方面非常有用,比如JAVA中的一些以前的接口要想在新的JDK中使用,就可以通过适配器模式来转换接口,实现兼容性。总而言之,适配器模式的使用场景主要是:

(1)系统需要使用现有的类,而这些类的接口不符合系统的接口;

(2)想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作;

(3)两个类所做的事情相同或相似,但是具有不同接口的时候;

(4)旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候;

(5)使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。

    适配器模式的优点是:

(1)通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。

(2)复用了现存的类,解决了现存类和复用环境要求不一致的问题。

(3)将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码。

(4)一个对象适配器可以把多个不同的适配者类适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

    适配器模式的缺点是:对于对象适配器来说,更换适配器的实现过程比较复杂。

    适配器模式的实现方式有多种,一般可分为类适配器与对象适配器,还有一种特殊的缺省适配器。

    类适配器是通过多重继承的方式来实现,由于有些语言不支持多重继承,并且有一个设计原则建议多用组合少用继承,所以类适配器并不推荐使用,但是类适配器由于是使用继承,所以可以通过适配器来覆盖被适配者的函数,从而不需要重新实现整个被适配者;对象适配器是通过组合的方式来实现,所以比较常见,其弹性较好;缺省适配器很少见,主要用于仅仅实现感兴趣的函数。

    下面我们来看一下一个例子,该例由三个文件组成,依次是:AdapterPattern.h、AdapterPattern.cpp、AdapterPatternTest.cpp。

// 适配器模式
// AdapterPattern.h文件

#ifndef ADAPTER
#define ADAPTER

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>

using std::string;
using std::cout;
using std::endl;
using std::vector;

// 适配器模式中客户需要的目标对象的抽象类
class Duck
{
public:
	Duck(){}
	virtual ~Duck(){}

	virtual void quack() = 0;
	virtual void fly() = 0;
};

// 适配器模式中被适配对象的抽象类
class Turkey
{
public:
	Turkey(){}
	virtual ~Turkey(){}

	virtual void gobble() = 0;
	virtual void fly() = 0;
};

// 适配器模式中客户需要的目标对象的具体类
class MallardDuck : public Duck
{
public:
	void quack();
	void fly();
};

// 适配器模式中被适配对象的具体类
class WildTurkey : public Turkey
{
public:
	void gobble();
	void fly();
};

// 类适配器
class TurkeyClassAdapter : public Duck, public WildTurkey
{
public:
	void quack();
	void fly();
};

// 对象适配器
class TurkeyObjectAdapter : public Duck
{
public:
	TurkeyObjectAdapter(Turkey *turkey)
	{
		this->turkey = turkey;
	}

	void quack();
	void fly();
private:
	Turkey *turkey;
};

#endif

// AdapterPattern.cpp文件

#include "AdapterPattern.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;
}

// 类适配器
void TurkeyClassAdapter::quack()
{
	gobble();
}

void TurkeyClassAdapter::fly()
{
	// 由于火鸡飞行距离较短,而鸭子需要较长的飞行距离
	// 所以这里飞行五次
	for (int i = 0; i < 5; i++)
	{
		WildTurkey::fly();
	}
}

// 对象适配器
void TurkeyObjectAdapter::quack()
{
	turkey->gobble();
}

void TurkeyObjectAdapter::fly()
{
	// 由于火鸡飞行距离较短,而鸭子需要较长的飞行距离
	// 所以这里飞行五次
	for (int i = 0; i < 5; i++)
	{
		turkey->fly();
	}
}

// AdapterPatternTest.cpp文件

#include "AdapterPattern.h"

void main()
{
	// 类适配器测试
	cout << "------------------------------------------" << endl;
	TurkeyClassAdapter turkeyClassAdapter;
	turkeyClassAdapter.quack();
	turkeyClassAdapter.fly();

	// 对象适配器测试
	cout << "------------------------------------------" << endl;
	Turkey *tukey = new WildTurkey();
	TurkeyObjectAdapter turkeyObjectAdapter(tukey);
	turkeyObjectAdapter.quack();
	turkeyObjectAdapter.fly();
	delete tukey;
	tukey = NULL;

	cout << "------------------------------------------" << endl;
}

    该例的运行结果如图1所示,UML类图如图2所示。

设计模式系列(十)适配器模式(Adapter Pattern)_第1张图片
图1 运行结果

设计模式系列(十)适配器模式(Adapter Pattern)_第2张图片
图2 UML类图

    该例中包含了类适配器和对象适配器两种实现方式,其中TurkeyClassAdapter类是类适配器,通过图2可以看出该适配器继承了Duck类和Turkey类,即多重继承;而TurkeyObjectAdapter类是对象适配器,通过图2可以看出该适配器继承了Duck类,其中包含了一个Turkey的指针,用来指向一个具体的Turkey对象,即组合。通过图1的运行结果可以看出这两种实现方式的运行结果是一样的。

    该例的主要意思是顾客希望得到一个Duck,但是目前只有Turkey的具体实现和Duck的接口,所以只能通过适配器将Turkey包装一下,使其看起来就是一个Duck,从而客户看到的还是Duck,而实际上是Turkey,即实现了接口转换。

    最后给出一个网上十分常见的缺省适配器的例子,了解一下即可,注意其中只是实现了自己感兴趣的函数f3()。

#include<iostream>
using namespace std;


class Target { 
public:
	virtual void f1(){}; 
	virtual void f2(){}; 
	virtual void f3(){};   
};

class DefaultAdapter : public Target 
{ 
public:
    void f1() { 
    } 

    void f2() { 
    } 

    void f3() { 
    } 
};

class MyInteresting :public DefaultAdapter
{ 
public:
     void f3(){       
		cout<<"呵呵,我就对f3()方法感兴趣,别的不管了!"<<endl;
    } 
};

int main()
{
	// Create adapter and place a request
	Target *t = new MyInteresting();
	t->f3();

	return 0;
}

    总之,适配器模式还是很用的,通常使用对象适配器。

你可能感兴趣的:(设计模式,C++,Pattern,Adapter,适配器模式,UML类图)