“我们来看看
PrintFruitName()函数的问题,它的一个比较明显的缺陷是效率。”老C用笔指着代码说道。
“是啊是啊,”有机会表现一把小P很是高兴,“这个函数做了线性查找,因此它的时间效率应当是n。”
“对,因此对比原版的switch...case...语句的常数时间效率,我们现在的设计在时间效率上有损失,你有什么好办法解决这个问题吗?”
“让我想想……”小P又开始发动脑筋,“我觉得我们可以使用一个散列表来储存这些信息,那么在查找的时候算法的时间复杂度就是常数了。”小P找到了解决办法很是高兴,就想着去白板上写下自己的得意之作。
“等等别急。”老C拦住小P,“只要知道了解决之道,代码是微不足道的,你先不要着急写代码……”老C对小P的反应速度有些惊讶,“我觉得你的基础挺好的啊,就是缺乏正确的引导……”
“呵呵,谢谢啊,其实我就是比较油菜而已。”听了老C的评论小P有些囧,不知道他是表扬还是批评。
“我们不着急对这一版进行改写,现在你试着使用C++来完成上面的代码。”老C给小P出了新的题目。
“嗯,我看看啊。”小P开始思索起来,“但是有什么不同吗?如果使用C++的话,我可能还是会写出第一版那样的代码啊……”
“所以我说你缺乏正确的引导啊,”老C有些感慨,“因为C++包含了C的部分,因此你完全可以用C的思维方式在C++下编码,反应到代码风格上,就是第一版风格的代码在C++代码中频繁出现……”
“槑,”小P有些莫名其妙,“那么应该怎么做呢?”
“嗯,我先简单的写写,然后我们来review。”老C在白板上划了一道线,将白板分为两部分,在原来的代码部分上面写下C,然后在另一个空白部分的头部写下C++。
class Fruit
{
public:
virtual ~Fruit() {}
public:
virtual void printName() = 0;
}
class Orange : public Fruit
{
public:
virtual void printName() { cout << "orange" << endl;}
}
class Apple : public Fruit
{
public:
virtual void printName() { cout << "apple" << endl; }
}
class Banana : public Fruit
{
public:
virtual void printName() { cout << "bnana" << endl; }
}
void PrintFruitName(const Fruit& fruit)
{
fruit.printName();
}
int main()
{
/* Fruit factory function. */
Fruit* fruit = GetFruit();
PrintFruitName(*fruit);
delete fruit;
return 0;
}
“唔,”老C揉揉手,“差不多就是这样啦,一个很简单的实现……与现实代码相去甚远……”他又想了想,“在实际情况下我们是不会这样编码的,但在这里只是说明一下思维的差异性。”
“给我讲讲吧。”小P等待下文。
“在这个实现里我们使用了C++的多态特性,也有一种说法是晚绑定……但是无论怎么说,其根源也是信息隐藏。”老C开始比较C和C++的实现,“我们已经比较过第一版和第二版的C实现,发现信息隐藏是进行设计的一个关键点……”
“等等老C,什么叫多态?什么叫晚绑定?”
“哦,我们来看看PrintFruitName()函数,你能说清楚这个函数具体实现了哪些需求?”如何简单的解释这些术语让老C觉得有些头痛。
“从代码看它实现了对fruit对象名称的打印……”小P看了看代码,“但是具体如何做的我看不出来,而且打印的内容与函数的输入参数有关,不同的参数会有不同的结果……”
“没错!保持统一的接口,而具体行为依照对象而定,不同的对象有不同的行为,这个就是对多态的简单解释。”老C觉得小P还是有些悟性的,“具体在C++语
言中,多态通过指针和引用来表现。即接口使用父类的指针或引用来表明抽象的统一的接口,而行为根据父类指针或引用所指向具体子类对象,不同的对象表现出不
同的行为,此乃C++实现多态的风格,具体来说我们在编程的时候需要使用指向父类的指针或者引用,然后在子类中改写父类中的虚函数,最后再把子类对象赋值
到父类指针或引用上。如果现在我们需要再增加一个梨这样的水果,你会怎么做?”
“好像很简单了?”小P试着在白板上写下如下代码。
class Pear : public Fruit
{
public:
virtual void printName() { cout << "pear" << endl; }
}
"然后怎么办?我可能还需要改写GetFruit()函数,使得他可以增加一个返回的对象类型?"小P很细心的发现一个问题,“但是我觉得我们很难在
GetFruit()函数中避免类似if..else...或者switch...case..之类的逻辑选择分支……等等,好像我们实现的第二版C语言
代码也存在类似的问题,就是GetFruitInfo()中也无法避免多选择的判断分支……”
"没有错!但是起码我们对PrintFruitName()函数的维护会好很多。"老C很是赞同小P的观察力,“这个是另外的问题,涉及到一些
factory模式,我们以后再讨论……无论怎么样,我们先来评判一下现在C++的实现。因为C++的虚函数实际上采用散列表的数据结构实现,所以我们的
C++程序执行效率会比白板另一边的第二版C代码好一些。我觉得我以后一定会和你讨论一下C++的虚函数的实现的,但是今天我们先把这个问题放一边。”
“看来C++还有很多东西是我不了解的啊。”小P开始觉得自己C++课程好像是白上了。
“我们来比较一下三个版本的实现。”老C开始回顾早上的讨论,“你发现什么问题没有?”
“好像一段代码对其具体实现了解的越少,它的维护性就会越好?”小P有些猜测。
“呵呵,的确,那么我们通过各种不同的方法达到了什么样的看似相同的目的?”老C开始掉小P的胃口。
“信息隐藏?”小P不太确定。
“信息隐藏是手段,但不是目的。”老C很确定的否决掉小P,“我们达到的目的是
控制问题的规模!”
老C觉得有必要给小P讲讲哲学:“我们写软件的目的是为了解决现实生活中的具体问题,没错吧?”
“没错,的确是这样,可是这个和C++有什么关系的?”小P觉得有些莫名其妙。
“那么你觉得使用高级的语言、先进的设计和合理的开发流程,问题的复杂度会降低吗?”
“那是啊,问题的复杂度当然会降低啊。”
“唉,错了,问题的复杂度不会降低的,因为问题的复杂度是客观存在,不会因为人主观的原因而改变!”
“槑!”小P有些被震住了,他以前还真是没有考虑过这样的问题,“那么为什么我觉得C解决问题比汇编简单呢?”
“那是因为问题的规模被控制了!”老C开始强调,“因为C的编连器暗地里帮你做了很多事情来控制问题表现给你的规模,使你感觉好像问题变简单了——其实是你面对的问题规模变小了。打个比方,”老C在白板上找出了上午最早的程序。
typedef enum tagFRUIT{ORANGE, APPLE, BANANA} FRUIT;
void PrintFruitName(FRUIT fruit)
{
switch (fruit)
{
case ORANGE:
pirntf("orange\n");
break;
case APPLE:
printf("apple\n");
break;
case BANANA:
printf("banana\n");
break;
default:
return;
}
}
int main()
{
FRUIT fruit;
/* Get the information of fruit. */
fruit = GetFruitInfo();
/* Print the name of the fruit. */
PrintFruitName(fruit);
return 0;
}
“看看吧,根据你刚才的发现,是不是如果需求发生变化时,GetFruitInfo()和PrintFruitName()这两个函数都要发生变更?”老C指着代码问小P。
“是啊,没有错。”
“现在我们代码的规模还很小,如果我们在多处需要涉及到水果的信息,那么根据这种风格,如果需求发生变更时,是不是每个地方都需要对源代码进行修改?”老C开始循循善诱。
“嗯,好像是的。我们这里只是打印了水果的名称,如果我们还需要水果的形状,水果的颜色,哦,好像我们维护时候的复杂度会按照问题的规模成倍的增长。”
“是啊是啊,这就叫牵一发而动全身。”老C总结道,“那么第二个做法呢?”
“好像可以好一些,起码我们只用修改某些表格,哦,还要修改哪个GetFruitInfo()的函数,但是起码问题的扩散没有那么严重了……”小P现在隐约觉得自己好像脑袋里面有只手,快要抓住什么东西却又抓不到,有些迷蒙起来。
“但是维护表格的工作量也不小啊。”老C补充道,“那么最后一种做法呢?”
“我个人感觉好像C++编连器在帮助我们维护这些表格?”小P好像猛的明白过来,“比如将虚函数的实现隐藏在编连器的后面……”他又开始有些迷蒙……
“是的,是这样的。”老C点点头,“我们实际上面对问题的复杂度并没有改变,只是由于语言的帮助,我们可以设计出一些代码来限制我们接触问题的规模,把一
个复杂的问题逐步划分到我们自己可以理解的规模上来,这样好像问题变简单了一样。”老C接着说道,“这就是我为什么说我们所使用的语言会影响到我们思考问
题的方法,而我们思考问题的方法会反过来影响我们编码的风格。”
小P突然感觉自己来到了一个更宽阔的世界,好像自己突然明白了什么又好像不是很明白……小P开始觉得C++充满了神秘和乐趣,下决心一定要把C++学好。
“老C,和你聊聊太有收获了,我要把这些代码抄下来回去再看看,体会体会,”小P做激昂状,“我一定要在3年内学好C++……”
“等等,3年时间好像太短了吧?”老C有些被雷到了。
“?”
“建议你看看《Teach Yourself Programming in Ten Years》这篇文章吧,急是急不来的。”老C觉得年轻人就是浮躁。
“哦?好,那我回宿舍后查查这篇文章。但是以后你要多教教我啊。”
“互相讨论,互相学习。”老C谦虚道,“C++还有template种类的编程风格,但是我想我们还是讨论到此吧,时间也不早了,我们回去吧。”
“好啊,回去打一盘魔兽,看看谁更厉害!”小P决定也给老C当一回老师过过瘾!
(欲知后事如何,且听下回分解)