适配器模式(ADAPTER)

适配器模式也是在开发一些业务框架时经常会使用到的模式。

1. STL 的适配器

其实该模式在我们使用 STL 标准模板库时也经常碰到过。比如,not1、not2 去翻适配器,bind1st、bind2nd 绑定适配器等,我们回顾下绑定适配器的使用,如下代码所示:

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>

using namespace std;


void print_vector1(int number)
{
	std::cout << number << " ";
}

void print_vector2(int number, int additional)
{
	std::cout << number << " " << additional;
}


void test()
{
	vector<int> v = { 10, 20, 30, 40, 50 };

	for_each(v.begin(), v.end(), print_vector1);
	for_each(v.begin(), v.end(), print_vector2);
}


int main()
{
	test();

	return 0;
}

上面示例代码中,24行代码运行是没有问题的。这是因为 for_each 算法要求传入的函数有且只能有1个参数。如果你定义的函数有 2 个参数的话,for_each 算法就会报错,报错的原因可以从其算法实现来知晓原因:

template <class _InIt, class _Fn>
_CONSTEXPR20 _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    for (; _UFirst != _ULast; ++_UFirst) {
        _Func(*_UFirst);
    }

    return _Func;
}

如果我们仅仅是为了让 25 行代码能够正常执行,可以强制将其函数指针类型进行转换,但是需要注意的是,这种强转后的结果可能会超出我们的预期。如下所示:

for_each(v.begin(), v.end(), (void(*)(int))print_vector2);

上述代码执行后的结果:

 122788047220 -65409933630 -65409933640 -65409933650 -654099336

虽然不报错了,但是结果也不是我们想要的。所以,我们经常会用到 STL 中的绑定适配器。

for_each(v.begin(), v.end(), bind1st(ptr_fun(print_vector2), 100));
for_each(v.begin(), v.end(), bind2nd(ptr_fun(print_vector2), 100));

for_each 算法要求一个参数的函数,而 print_vector2 是 2 个参数的函数,我们可以通过适配器将其转换为 1 个参数的函数,这样就能符合 for_each 算法的要求。从这里,同学们应该能够明白了,适配器最普遍的作用就是对函数进行适配,将其接口转换为符合目标算法要求的函数。

上面的示例代码中,ptr_fun 本质也是一个适配器,只不过其作用是将一个普通函数转换为函数对象(仿函数),因为在 STL 中绑定适配器要求的参数必须是函数对象。

2. 适配器实现原理

我们有很多不同场景,需要实现的适配器也有所不同。STL 中的 bind1st、bind2nd 是基于模板来实现的适配器,其可以适应不同数据类型的场景,同学们可以参数 vs 版本 STL 的源码实现来学习编码技巧。我们可以简单给同学们介绍下如何基于面向对象思想来实现适配器。

在写之前,先明确下目标和意图:适配器模式用于将接口转换为另外一种接口,解决原本接口不兼容的问题。业务函数(要求传递一个数字和一个带返回值的单个参数的函数)如下:

template<class T>
void do_logic(int number, T show)
{
	std::cout << show(number) << std::endl;
}

我们使用模板来定义 do_logic 函数,是由于该函数既可以传递函数指针,也可以传递函数对象,我们需要传递给 do_logic 的函数参数如下:

int my_show1(int a)
{
	return a;
}

int my_show2(int a, int b)
{
	return a + b;
}

很明显,my_show2 并不适配 do_logic 第二个参数的要求。此时,我们就可以实现一个适配器,来将 my_show 接口进行转换。

class Adaptor
{
public:
	Adaptor(int(*show)(int, int), int addtional)
	{
		this->show = show;
		this->addtional = addtional;
	}

	int operator()(int number)
	{
		return this->show(number, this->addtional);
	}

public:
	int(*show)(int, int);
	int addtional;
};



void test1()
{	
	do_logic(100, my_show1);
	do_logic(100, Adaptor(my_show2, 200));
}

Adaptor 就是用于将 my_show2 函数进行转换的适配器类,该类接受相应的参数并返回一个能够被 do_logic 正确调用的函数对象,从而完成将 my_show2 接口适配到 do_logic 目标函数的目的。从代码实现,同学们应该能够很清晰的看到,所谓的适配就是对不符合要求的接口进行一层代码包装,在包装内部完成适配的转换过程。另外,同学们也要了解上面代码的局限性,即:它只能适配参数为 int 类型的函数。如果想编写支持不同类型的适配器,需要使用到 C++ 模板泛型技术。

未经允许不得转载:一亩三分地 » 适配器模式(ADAPTER)