从前,有一个藏族小姑娘,名叫卓玛。家里很穷,靠拾垃圾(Trash)为生。每天早出晚归,辛勤劳动。 卓玛白天只捡两种垃圾,废纸(Paper)和金子(Gold)(哇,没搞错,金子也是垃圾),晚上回家后,便把垃圾分类放置,废纸放进纸箩(PaperBin),金子放进金库(GoldLib)(见过家里有金库的小姑娘去捡垃圾的吗?)。好了,故事讲到这里,暂且打住。干点正事。
进入正题之前,强烈希望读者读过(Think in C++ 2nd Edition Volume 2, Chapter 10 Design Patterns)
And you can download it at http://pcbook.51soft.com
在《 Think in C++》中,作者用OO中的double dispatch方法实现垃圾的分类。
而我用GP方法。
class Trash //垃圾类
{
};
class PaperBin; //垃圾箱(放废纸)
class GoldLib;//垃圾箱(放金子)
class Paper:public Trash //废纸类
{
};
class Gold:public Trash //金子类
{
};
class PaperBin:public vector<Paper>
{
};
class GoldLib:public vector<Gold>
{
};
class Management //小姑娘卓玛是Management的一个实例
{
private:
PaperBin _myPaperBin; //小姑娘卓玛家里的垃圾箱,只有卓玛可以打开
GoldLib _myGoldLib;
};
我们要做的事是把垃圾自动分类。废纸放进纸箩(PaperBin),金子放进金库(GoldLib)
讲一点编程方面的题外话,编程风格(coding style)。个人认为, 编程风格影响代码质量,最终影响产品质量。我的风格是:
1。类名第一个字母大写。如:Trash, Gold。千万别用拼音字母。如:Laji, Jingzhi。拼音字母在程序中的可读性太差。英文不好的朋友可以用金山词霸先翻译一下。
2。定义成员函数或变量时,首先定义为private。
3。私有成员前加下划线。如:_myPaperbin
4。先定义成员变量。后面要讲到为什么。
编程风格还包括很多方面,可惜上面的代码中没有用到。我又不想没有例子泛泛而谈。有兴趣的朋友可以参考<<Windows coding style>> Microsoft Press
讲完编程风格,说几句设计思路的问题。
程序总是从无到有,从毫不相关到相互联系,从简单到复杂,功能由少到多,逐步丰满起来。我来讲讲怎么做。
1。从无到有:先把Requirement中的名词挑出来,建立一个个class。先别管他们之间的关系。在上面的例子里,我们先建几个空类。如:Trash, Paper, Gold, Paperbin, GoldLib. Management。
2。从毫不相关到相互联系:考虑他们之间的关系。如:Paper,Gold 和Trash是继承关系,Management和 PaperBin,GoldLib是拥有关系。
3。考虑每个类的状态(state):也就是定义成员变量。Trash,Paper,Gold 我们还不知道要干什么。定义为空。Management用来管理垃圾箱,所以它有两个成员变量。分别是_myGoldLib,_myPaperBin。
4。考虑他们之间的相互作用。这只需要 把Requirement中的动词挑出来就可以了。在上面的例子里,小卓玛把废纸放进纸箩(PaperBin),金子放进金库(GoldLib)。我们定义一个动作,注意,一个 小卓玛做的动作,放进 , add(Paper,paperBin), add(Gold, GoldLib)。
5。泛化思考:小卓玛作为一个管理人员,事必躬亲当然是好事,但也太累了。我们能否给小卓玛买一台自动垃圾分类机。这样,小卓玛只要放进(垃圾) add(trash) (而不是add(Paper,paperBin), add(Gold, GoldLib) )就可以了。自动垃圾分类机把不同的垃圾放到不同的容器中。这是本章的中心内容,待会再详谈。我这儿讲的是设计思路。好了,我们为Management增加一个成员函数 add(trash)。
Management 看起来象这样:
class Management
{
private:
PaperBin _myPaperBin;
GoldLib _myGoldLib;
public:
template<class T>
void Add(T aTrash);
};
6。细化。也就是说实现 add(trash)。并根据不同的情况,提供最优实现。( 泛泛而谈,没有例子,您可要骂我了。如果您分析过STL中的allocator,您就会知道, allocator可以根据不同的情况(trivial,nontrivial特性(traits)),提供最优的内存管理方案。建义参阅侯捷的大作《STL原码分析》。网上有本书前5章供预览,足够让您了解STL的原理了。没必要去买整本书。兄弟还认为,候捷的中文不太好,古文一样的文法,还不如读E文来的爽快)
GP(Generic Programming,下同)的中心思想是为了实现代码reuse。为了做到代码reuse,一个必要条件是降低类之间的耦合度。每个类只管自己应该管的事(个人自扫门前雪,休管他人瓦上霜(道德品质有问题?))。如:Paper只应该管自己的事, 现在没事,好,歇着(空类)。别象《Think in C++》中的Paper一样,整天琢磨如何把自个儿add到垃圾箱中去(Paper要实现一个add虚函数)。《Think in C++》中的PaperBin也不是东西,您就是一个容器,没事就该歇着,别象妓院的老鸨一样,整天倚在门口,对每个路过的trash说,进来,进来。还把不满意的给踢出去(没钱也敢逛妓院?)(Paperbin也要实现一个add虚函数,并和paper 中的add配合以实现垃圾分类的目的)。您可以看到,《Think in C++》中的PaperBin 和 paper都做了本不该它们做的事。在兄弟的代码中,每个类个干各的事,互不干扰。
好了,原理和方法讲得差不多了。现在要实作了。丑媳妇早晚要见公婆。
定义头文件,没什么好说的
#include<vector>
#include <iostream>
using namespace std;
请大家注意,迄今为止我们都没定义Paper,Gold要干什么。所以,它们都是空的。
又请大家注意 typedef PaperBin aBin; 这句话。它用来告诉它的管理者(Paper 不知道它的管理者是谁,这样可以降低Paper和其管理者之间的耦合度),如果要把Paper存放起来,请放在PaperBin 中。同理, typedef GoldLib aBin; 告诉它的管理者,如果要把Gold存放起来,请放在GoldBin 中。说穿了,这就是Traits技术。
class Trash
{
};
class PaperBin;
class GoldLib;
class Paper:public Trash
{
public:
typedef PaperBin aBin;
};
class Gold:public Trash
{
public:
typedef GoldLib aBin;
};
class PaperBin:public vector<Paper>
{
};
class GoldLib:public vector<Gold>
{
};
下面两个类是本章的难点,它们用来判断两个类型(T,U) 是不是同一个类型。T=U?
如果是,enum中的 exists为 true,否则exists= false
详细说明在C/C++ Users Journal , Generic<Programming>:Mappings between types and values.
template<int v>
struct Int2Type
{
enum{value=v};
};
template<class T, class U>
class Conversion
{
typedef char Small;
struct Big {char dummy[2];
};
static Small Test(U);
static Big Test(...);
static T MakeT();
public:
enum{ exists=(sizeof(Test(MakeT()))==sizeof(Small))};
};
让我们看看小卓玛干了些什么
首先,Management 只提供一个接口,add(trash); add(trash) 通过呼叫_classify()来分类,_classify()呼叫_Add() 把trash放入指定的垃圾箱中
class Management
{
private:
PaperBin _myPaperBin;
GoldLib _myGoldLib;
template<class Trashs, class Bin>
void _classify(Trashs trash, Bin bin)
{
_classify 判断输入的垃圾箱是否和trash所指定的垃圾箱吻合。如是,把trash放入所指定的垃圾箱,如否,什么也不做,返回。
cout<<Conversion<Trashs::aBin ,Bin>::exists<<endl;
_Add(trash,bin,Int2Type<Conversion<Trashs::aBin ,Bin>::exists >());
};
template<class Trash, class Bin>
void _Add(Trash trash, Bin bin,Int2Type<true>)
{
把 trash放入垃圾箱。
bin.push_back(trash);
cout<<"success adding"<<endl;
}
template<class Trash, class Bin>
void _Add(Trash trash, Bin bin,Int2Type<false>)
{
什么也不做,返回。
cout<<"failed adding"<<endl;
}
public:
template<class T>
void Add(T aTrash)
{
把每个 trash放入各个垃圾箱试试,如果找不到合适的垃圾箱,编译会报错。这样可以减少出错的机会。如果用OO方法的话,您只有等到运行时才知道程序错了。
_classify(aTrash,_myPaperBin);
_classify(aTrash,_myGoldLib);
};
};
int main()
{
Management zhuoma;
生成两种垃圾
Paper apaper;
Gold agold;
把垃圾放入垃圾箱
zhuoma.Add(apaper);
zhuoma.Add(agold);
return 0;
}
程序的说明就写到这里。下面,让我们比较一下《Think in C++》中的方法和GP方法。
1。扩充性:小卓玛要增加一个工作,拾废玻璃。我们只要作如下改动:
增加两个类:
class GlassBin;
class Glass
{
public:
typedef GlassBin aBin;
};
class Glassbin:public vector<Glass>
{
};
修改Management:
增加一个成员变量 GlassBin _myGlass;
增加一行到 Add()
template<class T>
void Add(T aTrash)
{
_classify(aTrash,_myPaperBin);
_classify(aTrash,_myGoldLib);
_classify(aTrash,_myGlassBin)
};
保持其他部分不变。爽吧。
反观《Think in C++》,您可以试着增加一个工作,看看有没有这么简单,关键是条理有没有这么清晰。
2。效率:GP方法不用动态联编,没有vtable。所以在时间上和空间上都好过OO方法。
3。安全性:GP方法利用编译器的强大功能,在编译时揪错。比OO在运行时揪错更早地发现问题。防止因测试不足而把bug带入产品。
4。更简单的用户接口:用户要知道的只是Management.Add(trash)。增加新类不会改变 用户接口。
好了,想到的就这么多。不过,故事还没完。我们的小卓玛每周要把垃圾卖到垃圾回收站去。那时,我们还要考虑废纸和废玻璃的价钱,还有金子的汇率(要和银行打交道)。怎么做呢?下回分解(今天我们的代码仍然有效,让大家看看GP扩充代码功能的威力)
说几句废话:
本人基于黑客的基本理念:传播知识,共同提高。作如下申明:
1。您可以在非盈利的情况下,自由复制,修改,传播。
2。请您把这两句废话包含在其中。
另外,有朋友想和我一起续写卓玛的故事,请email给我。bu3bu4@263.net。条件是,您的大作没有分文报酬。
累过的感觉真好。