25.虚拟工厂模式

前言

 
工厂模式在C++中体现为构造函数虚拟化,即针对不同的已知对象,调用不同的构造函数,生成不同的新对象。


问题实例

 
假设现有一个程序负责生成新闻报道,每篇报道由文字或者图片组成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class NLComponent {//abstract class
public:
...
};
class TextBlock:public NLComponent {
public:
...
};
class Graphic: public NLComponent {
public:
...
};
class NewsLetter {
public:
...
private:
list<NLComponent*> components;//以链表存储数据
};

classes之间的关系如下:
image_1cc4spmjh1ivl249m7r1o1jo3319.png-107.4kB
我们给NewsLetter class添加一个以istream作为参数的构造函数,其伪代码大致如下:
1
2
3
4
5
6
7
8
9
10
11
class NewsLetter {
public:
NewsLetter(istream& str);
...
};
NewsLetter::NewsLetter(istream& str){
while (str) {
...//从 str 读取下一个component对象;
...//将component对象加入链表
}
}

又或者,我们为此建立一个独立函数readComponent:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class NewsLetter {
public:
...
private:
//为建立下一个 NLComponent 对象从 str 读取数据,
//建立 component 并返回一个指针。
static NLComponent * readComponent(istream& str);
...
};
NewsLetter::NewsLetter(istream& str){
while (str) {
components.push_back(readComponent(str));
}
}

显然,readComponent根据str建立了一个新对象,其类型是text或者graphic.它的行为类似于构造函数,但又能构建不同的对象,因此被称为虚构造函数。其特性在于,根据不同的输入构造出不同的对象。(虚拟工厂模式)


虚拷贝构造函数

 
虚拷贝构造函数返回一个指针,指向调用该函数的对象的新拷贝,一般命名该函数为copyself或者clone,其大致实现细节如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class NLComponent {
public:
virtual NLComponent * clone() const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual TextBlock * clone() const{
return new TextBlock(*this);
}
...
};
class Graphic: public NLComponent {
public:
virtual Graphic * clone() const{
return new Graphic(*this);
}
...
};

可以看出,虚拷贝构造完全调用了拷贝构造,因此具备其拥有的一切特性,诸如引用计数之类。
值得注意的是,虚拷贝构造的一大特点:派生类重定义的虚函数不必与基类对应函数具备一致的返回类型,这也就是我们上例中返回Text*的原因。

这种虚拷贝构造函数让对象的正常拷贝构造变得很容易:

1
2
3
4
5
6
7
8
9
10
11
12
class NewsLetter {
public:
NewsLetter(const NewsLetter& rhs);
...
private:
list<NLComponent*> components;
};
NewsLetter::NewsLetter(const NewsLetter& rhs){
for (auto it =rhs.components.begin();it != rhs.components.end();++it) {
components.push_back((*it)->clone());
}
}

虚拷贝构造函数的最大特性在于,无论对象的真正类型是什么,它都能轻易地完成拷贝。


非成员函数虚拟化

 
我们可能希望存在这么一个非成员函数,能够根据参数的不同类型而执行不同的行为。
以上文中的NewsLetter为例,我希望为text或graphic实现一个输出操作符,但operator<<默认把ostream&作为它的lhs,那注定了它无法变成成员函数。
解决方案是建立一个虚成员函数print,在operator<<中调用print函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class NLComponent {
public:
virtual ostream& print(ostream& s) const = 0;
...
};
class TextBlock: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
class Graphic: public NLComponent {
public:
virtual ostream& print(ostream& s) const;
...
};
inline
ostream& operator<<(ostream& s, const NLComponent& c){
return c.print(s);
}

显然,虚拟非成员函数的实现过程如下:

  1. 完成一个虚成员函数
  2. 在非成员函数中调用虚成员函数
  3. 为了避免额外的调用开销,inline该虚非成员函数

总结

 
以上均为以单一参数完成虚拟化的实例,根据多参数完成虚拟化详见More Effective C++ 31。