法则04:语法潜台词
每种编程语言都有对代码进行约束的一些语法,当然它们还传递着一些附加的信息。
● 函数入参
其中最为常用的应该是C/C++中的const关键字。而在Java中与之对应的是final关键字。
当需要表达一个函数的参数是入参时,可以加上const或final关键字。比如下面的C/C++代码。
或如下Java代码。
这种表达方式比下面这样通过注释来说明更有约束力。
因为新任的软件设计师稍不注意就修改了cmd的内容,但编译器并不会报错。此外对于Java语言,当函数参数为对象时,都是传引用的方式,效率很高。对于C++语言,当入参是一个对象时,需要再加一个传引用的修饰符“&”。
这样可以免去一次参数传递时的赋值拷贝,提高了效率。特别是入参为数组时,一定要加“&”修饰符(参考“法则14:关注性能热点”)。
● 构造函数
对于一个类,其构造函数大部分情况都声明为public,这表明该类的对象可以随时进行实例化,比如下面代码中可以直接定义一个Person的对象。
如果一个类的构造函数声明为protected,表明该类必须要经过派生后才能够使用。在“法则 13:规避短板”一节中介绍的可重用的基类ProcRuner,其构造函数就声明为protected。代码如下。
因为该类必须经过派生后才能复用,派生类必须重写两个虚函数。代码如下。
如果一个类的构造函数被声明为private,那说明它既不能被派生,也不能被直接实例化,例如“法则 31:灵活注入对象”中的ModuleMgr类。它很可能为单例(singleton)模式,同时会提供一个配套的静态成员函数GetInst以供获取该类的全局唯一实例。代码如下。
● 公有继承
此外对于继承体系,也有其内在的表达。公有继承传递的信息是“is-a”的关系。比如派生类Eagle公有继承自基类Bird,C++语言中使用public关键字表示公有继承。
对于Java语言,使用extends表示公有继承。
这是面向对象设计中非常重要的一个概念。比如上面代码描述的信息是:鹰“是一种”鸟类。当然,读到这里希望读者不要浅尝辄止,有两个要点需要特别注意。
第一:对“是”字的理解要正确,“是”字不能理解为“等于”。比如下面这个派生关系。
马和牛都派生自动物类,曾经有人热烈地讨论过CompareAnimal函数,如下所示。
当传入一个Horse和一个Bull的对象时,该CompareAnimal又该如何实现?
比体重还是比身高呢?因为马和牛没有可比性,因此这样的比较没有意义。但是他们简单地认为马“等于”动物,牛“等于”动物,既然都相等,就应该能拿来进行比较。
正确的理解应该为:“是”指的是一种被包含关系,是“属于”的意思,即前者“属于”后者的一类。比如我们所描述的学生是人、小轿车是车,都是指前者属于后者。即:学生属于人、小轿车属于车。
第二:公有继承的派生类和基类必须满足里氏替换原则。即所有使用基类对象的地方,都可以使用派生类对象进行替换。比如下面Android框架中,自定义的MyActivity派生自Activity。
系统启动时重写的onCreate方法会被回调,就是因为满足了这一原则。框架代码中操作的是Activity类,但传入的实际上是MyActivity的对象。以下代码描述了主要调用关系,剔除了无关代码。
当然,如果不满足里氏替换原则,则不能使用公有继承。比如下面Penguin(企鹅)类的派生关系。
因为企鹅不会飞,因此不能给Penguin类定义fly函数。由于作用于Bird类上的fly函数不能作用于Penguin类,不满足里氏替换原则,因此不能使用公有继承描述二者的关系。
需要特别注意的是,一些在现实中看似合理的派生关系,用代码描述不一定合理。在“法则 25:正确理解面向对象设计”一节中将对这个话题展开进行讨论。