适配器模式通常用于将一个类的接口转换为客户需要的另外一个接口,通过使用Adapter模式能够使得原本接口不兼容而不能一起工作的类可以一起工作。
这里将通过分析c++的标准模板库(STL)中的适配器来学习adapter模式。由于STL中的适配器过多,不可能全部都具体介绍,所有这里将简单介绍STL中存在的适配器,但将通过具体分析STL中istream_iterator适配器来分析adapter模式。
1. STL中的适配器
在STL中广泛使用了Adapter模式,主要有container adapter、iterator adapter、functor adapter
- container adapter: stack, queue 数据结构
- iterator adapter: front_insert_iterator, back_insert_iterator, istream_iteator, ostream_iterator
- functor adapter: bind, negate, compose 与对一般函数或者成员函数的修饰
2. Container Adapters
在STL中,stack、queue、priority queue是通过借助deque的所提供的接口来实现。
假设客户(希望使用stack、queue、priority queue的程序员)希望能够使用stack数据结构,则需要提供一个能够实现stack功能的类给客户,该类应该提供能够实现stack的push与pop操作的接口给客户调用,并且该类中不应该含有其他与stack能够提供的功能无关的接口。但是,当程序中不存在这样的类时,则可以通过借助已经存在的类deque,定义一个stack类,使用deque所提供的接口来实现stack类应该提供的接口,即上文所述—将一个类的接口转换为客户需要的另外一个接口。3. Iterator Adapters
迭代器本身是一种模式,迭代器模式使得客户可以抽象地看待各种数据容器,即将所有的数据容器(vector, list, deque等)看做一个数据流。流中的每个元素为一个数据(可以为各种类型的数据),通过对迭代器执行自增与自减操作,可以遍历流中的每一个元素。这样,无论一个数据结构是如何实现的,都可以将其看做一个流,如同数组一样,通过自增自减即可获取下一个元素或者上一个元素。
类似的从抽象角度看待数据容器的观点,使我们想起Unix中将文件、套接字等都统一看做文件来进行处理的观点,文件与套接字都支持从打开、关闭、读取数据、输入数据等操作,但是具体的实现细节不同,对于熟悉面向对象的人而言,能够立即想到面向对象中的多态,接口相同而具体行为不同。
在如今,通过对文件、套接字进一步抽象,将文件、套接字等具有输入与输出功能的物体抽象为输入与输出流,如在Java中的字节流、文件流、套接字等。我们同样可以遍历输入输出流中的每个元素,从这个角度讲,从输入流中读取数据就如同从容器中读取数据一样,向输出流中输入数据则如同向容器中增加元素一样,则在STL中能够适用于容器的算法应该也需要能够使用与输入输出流。istream_iterator与ostream_iterator即是为了能够让适用于容器的算法同样也能够适配于输入输出流而出现的。
void IstreamIteratorTest( ){ const int size = 10; auto print = [](int x) { std::cout << x << std::endl; }; std::vector vec; for (int i = 0; i < size; ++i) vec.push_back(i); std::vector copy_from_container(size); std::copy(vec.begin( ), vec.end( ), copy_from_container.begin()); std::for_each(copy_from_container.begin( ), copy_from_container.end( ), print); std::vector copy_from_istream(size); std::istream_iterator int_istream(std::cin), eos; std::copy(int_istream, eos, copy_from_istream.begin()); std::for_each(copy_from_istream.begin( ), copy_from_istream.end( ), print);}
在该程序中,copy_from_container拷贝vec容器中的元素,copy_from_istram通过从输入流中读取数据来拷贝到容器中,这里可以将容器vec和输入流都看做可以不断读取数据元素的流,通过将在流中读取的每一个元素都复制到copy_from_container或者copy_from_istream中,也可以看做读取到copy_from_container或者copy_from_istream输出流中,重点是这里使用了相同的算法copy,copy算法本来适用于容器的迭代器,通过对迭代器的自增、自减来遍历获取容器中的元素,而istream与ostream并不支持copy算法中对迭代器的取值与自增、自减等操作,通过使用Adapter模式,使能够使用与容器的算法同样也能够使用与输入与输出流,即—使用Adapter模式能够使得原本接口不兼容而不能一起工作的类可以一起工作。
// copy algorithmtemplateinline OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result, input_iterator_tag){ for (; first != last; ++first, ++result) *result = *first; return result;}
这里给出istream_iterator的实现:
templateclass istream_iterator;template bool operator==(const istream_iterator & x, const istream_iterator & y);template class istream_iterator { friend bool operator==<>(const istream_iterator & x, const istream_iterator & y);public: typedef input_iterator_tag iterator_category; typedef T value_type; typedef const T* pointer; typedef const T& reference; typedef Distance difference_type; istream_iterator( ) : stream(&std::cin), end_marker(false) { } istream_iterator(std::istream& s) : stream(&s), end_marker(false) { read( ); } reference operator*( ) const { return value; } pointer operator->( ) const { return &(operator*( )); } bool operator!=(const istream_iterator & rhs) { return !(*this == rhs); } istream_iterator & operator++( ) { read( ); return *this; } istream_iterator & operator++(int) { istream_iterator tmp = *this; read( ); return tmp; }protected: std::istream* stream; T value; bool end_marker; void read( ) { end_marker = (*stream) ? true : false; if (end_marker) *stream >> value; end_marker = (*stream) ? true : false; } bool equal(const istream_iterator& x) const { return (end_marker == x.end_marker) && (!end_marker || stream == x.stream); }};template bool operator==(const istream_iterator & x, const istream_iterator & y){ return x.equal(y);}
front_insert_iterator、back_insert_iterator与insert_iterator的出现是因为在某些情况下需要将对迭代器的赋值操作转换为push操作或者insert操作,不同之处在于插入的元素位置不同。如copy算法是将一个迭代器所指的数据拷贝到另一个迭代器所指的位置上,而如果想在该位置插入元素,而并不想对原来的该位置上的元素值进行更改,则需要使用使用插入操作替换赋值操作,这里就可以更加插入的位置不同而选择相应的front_insert_iterator、back_insert_iterator或者insert_iterator。
4. Functor Adapters
仿函数适配器主要用来修饰函数,为函数添加某些需要的功能,在函数式语言如Scheme中可以通过高阶函数实现。在c++中可以通过将每个函数实现为一个class来模拟这种行为。如通过函数g(x) = 3 * x, f(x) = x + 2构造出函数t(x) = f(g(x)),则可以使用Functor Adaptors来实现,下面给出Scheme与c++的实现方法,Java的实现方法与c++类似。
Scheme实现:
(define (func-t func-f func-g) (lambda (x) (func-f (func-g x))))(define (func-f x) (+ x 2))(define (func-g x) (* x 3))(print ((func-t func-f func-g) 3))
c++实现:
templateclass T {private: Func1 func1_; Func2 func2_;public: T(Func1 func1, Func2 func2) : func1_(func1), func2_(func2) { } int operator()(int x) { return func1_(func2_(x)); }};void FunctorTest( ){ auto func1 = [](int x) { return x + 2; }; auto func2 = [](int x) { return x * 3; }; std::cout << T (func1, func2)(3) << std::endl;}