设计模式:适配器模式(Adapter)

设计模式:适配器模式(Adapter)

适配器模式(Adapter)属于结构型模式(Structural Pattern)的一种。

结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构。

结构型模式可以分为类结构型模式和对象结构型模式:

  • 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。
  • 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。

模式动机

通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。

在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。

模式定义

适配器模式(Adapter)别名为包装器(Wrapper)模式,属于结构型模式。它既可以作为类结构型模式,也可以作为对象结构型模式。

适配器模式(Adapter)将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

模式结构

适配器模式(Adapter)包含如下角色:

  • 抽象目标(Target):一个抽象类,定义需要适配的标准接口。
  • 适配器类(Adapter):充当中间转换角色,该对象将源对象转换成目标接口。
  • 适配者类(Adaptee) / 源对象(Source):需要被适配的不兼容对象。
  • 客户(Client):通过目标类(Target)的接口访问它所提供的服务。

适配器模式有对象适配器和类适配器两种实现。

对象适配器:

在这里插入图片描述

类适配器:

在这里插入图片描述

时序图

在这里插入图片描述

模式实现

抽象目标 Target.h:

#ifndef _TARGET_H_
#define _TARGET_H_

class Target
{
public:
	virtual void request() = 0;
};

#endif // !_TARGET_H_

适配者类 Adaptee.h:

#ifndef _ADAPTEE_H_
#define _ADAPTEE_H_

#include <iostream>

class Adaptee
{
public:
	void specificRequest()
	{
		std::cout << "Adaptee::specificRequest" << std::endl;
	}
};

#endif // !_ADAPTEE_H_

对象适配器 ObjectAdapter.h:

#ifndef _OBJECT_ADAPTER_H_
#define _OBJECT_ADAPTER_H_

#include "Target.h"
#include "Adaptee.h"

class ObjectAdapter : public Target
{
private:
	Adaptee* m_pAdaptee;

public:
	ObjectAdapter(Adaptee* pAdaptee) :m_pAdaptee(pAdaptee) {};
	virtual ~ObjectAdapter()
	{
		delete m_pAdaptee;
	}

	virtual void request()
	{
		std::cout << "ObjectAdapter::request" << std::endl;
		m_pAdaptee->specificRequest();
	}
};

#endif // !_OBJECT_ADAPTER_H_

类适配器 ClassAdapter.h:

#ifndef _CLASS_ADAPTER_H_
#define _CLASS_ADAPTER_H_

#include "Target.h"
#include "Adaptee.h"

class ClassAdapter : public Target, public Adaptee
{
public:
	virtual void request()
	{
		std::cout << "ClassAdapter::request" << std::endl;
		this->specificRequest();
	}
};

#endif // !_CLASS_ADAPTER_H_

在单线程环境下的测试

测试代码,也可以说是 client:

#include <stdlib.h>

#include "ClassAdapter.h"
#include "ObjectAdapter.h"
#include "Adaptee.h"
#include "Target.h"

using namespace std;

int main(int argc, char* argv[])
{
	Adaptee* adaptee = new Adaptee();
	Target* tar1 = new ClassAdapter();
	Target* tar2 = new ObjectAdapter(adaptee);

	tar1->request();
	tar2->request();
	
	delete adaptee;
	delete tar1;
	delete tar2;

	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

在多线程环境下的测试

略。

模式分析

  • 对象适配器模式:继承抽象目标类(Target),实现目标接口的所有方法,使用复合的方式,拥有一个适配者类(Adaptee)的对象,调用需要适配对象的方法。
  • 类适配器模式:同时继承抽象目标类(Target)和适配者类(Adaptee),实现目标接口的所有方法,同时继承适配者类的实现,用以完成一些适配逻辑。

优缺点

优点:

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
  2. 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
  3. 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

类适配器模式还具有如下优点:由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还具有如下优点:一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点:

  1. 过多的适配器会导致系统结构复杂。
  2. 如果适配器没有实现好,可能会拖慢整个系统的性能。
  3. 滥用适配器模式会导致系统设计紊乱。

类适配器模式的缺点如下:对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。

对象适配器模式的缺点如下:类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用场景

在以下情况下可以使用适配器模式:

  1. 系统需要使用现有的类,而这些类的接口不符合系统的需要。
  2. 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

应用场景

  1. 电压转换器:不同国家的电压规格各异,同样功率的电器在不同的地方工作时需要不同的电压,电压转换器作为适配器,将不同电压转换成电器使用标准电压。
  2. 耳机转接头:有些手机没有耳机插口,需要使用转接头适配器,将耳机转换为手机支持的接口,实现对不同的耳机兼容。
  3. JDBC驱动程序:不同的数据库提供商(如SQL Server、Oracle、MySQL等)实现了不同的JDBC驱动接口,使用适配器模式可以将这些不同的接口适配为标准的JDBC接口,提高应用程序的可移植性。
  4. 日志框架:Java中有多个常用的日志框架,如Log4j、SLF4J等,不同的日志框架提供的API不同,使用适配器模式可以将这些不同的API适配为一个统一的接口,方便再程序中进行日志记录和管理。
  5. 第三方库或SDK:在使用第三方库或 SDK 时,可能由于它们实现的 API 不同而导致应用程序复杂,使用适配器模式可以将不同的 API 适配为统一的接口,简化应用程序的调用。

应用实例

当我们去国外旅游时,我们可能只会汉语,而当地人只会英语,那么这个时候就需要一个翻译员(翻译软件)来帮助我们。这就类似于适配器模式,通过一个适配器将一个不兼容的接口转成另外一个接口。下面以翻译为例,介绍一下类、接口、对象适配器。

  • 目标接口 Target:里面定义了一个 translate 方法。
  • 翻译类 Translator:就是适配者类,里面有 TranslateInZh 和 TranslateInEn 方法。
  • 对象适配器 ObjectAdapter 和类适配器 ClassAdapter。

对象适配器模式:

在这里插入图片描述

类适配器模式:

在这里插入图片描述

完整程序:

#include <iostream>
#include <stdlib.h>
using namespace std;

class Target
{
public:
	virtual void translate(string source, string target, string words) = 0;
};

class Translator
{
public:
	void translateInZh(string words)
	{
		if (words == "hello world!")
			cout << "你好世界!" << endl;
	}
	void translateInEn(string words)
	{
		if (words == "你好世界!")
			cout << "Translate in English: \"hello world!\"" << endl;
	}
};

class ClassAdapter : public Target, public Translator
{
public:
	virtual void translate(string source, string target, string words)
	{
		if (source == "中文" && target == "英文")
			this->translateInEn(words);
		else
			this->translateInZh(words);
	}
};

class ObjectAdapter : public Target
{
private:
	Translator *m_pTranslator;

public:
	ObjectAdapter(Translator *trans) : m_pTranslator(trans){};
	virtual ~ObjectAdapter()
	{
		delete m_pTranslator;
	}

	virtual void translate(string source, string target, string words)
	{
		if (source == "中文" && target == "英文")
			m_pTranslator->translateInEn(words);
		else
			m_pTranslator->translateInZh(words);
	}
};

int main()
{
	Translator* translator = new Translator();
	Target *tar1 = new ClassAdapter();
	Target *tar2 = new ObjectAdapter(translator);
	
	tar1->translate("中文", "英文", "你好世界!");
	tar1->translate("英文", "中文", "hello world!");
	cout << endl;
	tar2->translate("中文", "英文", "你好世界!");
	tar2->translate("英文", "中文", "hello world!");

	system("pause");
	return 0;
}

运行结果:

在这里插入图片描述

适配器模式和代理模式的区别

适配器模式的主要目的是使接口不兼容的对象能够协同工作。适配器模式允许将⼀个类的接口转换成另⼀个类的接口,使得不同接⼝的类可以协同工作。

代理模式的主要目的是控制对对象的访问。通常⽤于在访问真实对象时引入⼀些额外的控制逻辑,如权限控制、延迟加载等。

模式扩展

默认适配器模式(Default Adapter Pattern)

也称为缺省适配器模式。

当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。因此也称为单接口适配器模式。

接口适配器模式

主要适用于需要被适配的接口中,只有用到个别接口,也就是说不需要实现它的全部接口。通过一个中间抽象类或接口实现。

在这里插入图片描述

参考

  1. https://design-patterns.readthedocs.io/zh-cn/latest/structural_patterns/adapter.html#
  2. https://www.runoob.com/design-pattern/adapter-pattern.html
  3. https://blog.csdn.net/weixin_45433817/article/details/131037102
  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值