- 码农修行:编写优雅代码的32条法则
- 林文
- 1333字
- 2021-04-22 17:04:43
法则04:语法潜台词
每种编程语言都有对代码进行约束的一些语法,当然它们还传递着一些附加的信息。
● 函数入参
其中最为常用的应该是C/C++中的const关键字。而在Java中与之对应的是final关键字。
当需要表达一个函数的参数是入参时,可以加上const或final关键字。比如下面的C/C++代码。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_01.jpg?sign=1739583109-3KvHP0FeJknj4kZ5niQG6CDqPnI6FZC2-0-7359e3e499539b193053382b6ec7b7c4)
或如下Java代码。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_02.jpg?sign=1739583109-XYLIm4bMEvDKe1V2g6HaZJSlMTnXoL5V-0-fbce25f1c3beb32cff969be38f086e5d)
这种表达方式比下面这样通过注释来说明更有约束力。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_03.jpg?sign=1739583109-S6W6KKNibql5cvB4QfX2zI9aRF0G0EfO-0-3df2c7c58357af0d6ce1739edd26e813)
因为新任的软件设计师稍不注意就修改了cmd的内容,但编译器并不会报错。此外对于Java语言,当函数参数为对象时,都是传引用的方式,效率很高。对于C++语言,当入参是一个对象时,需要再加一个传引用的修饰符“&”。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_04.jpg?sign=1739583109-I6dpqk0YedCHAEbwHz3kd3KZ8oqifpVl-0-2207bf9ad9dd1463f840a348571e3bd5)
这样可以免去一次参数传递时的赋值拷贝,提高了效率。特别是入参为数组时,一定要加“&”修饰符(参考“法则14:关注性能热点”)。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_05.jpg?sign=1739583109-4T6wQEESgebZf7SHlGT0omtzQshmsrAI-0-b8c3386a9c690afa52a12e721e478253)
● 构造函数
对于一个类,其构造函数大部分情况都声明为public,这表明该类的对象可以随时进行实例化,比如下面代码中可以直接定义一个Person的对象。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/32_01.jpg?sign=1739583109-luqc974sdQB48NRrnFudiGGqudl3XZAK-0-80f04fa073148118ab511c4c60ef3cbc)
如果一个类的构造函数声明为protected,表明该类必须要经过派生后才能够使用。在“法则 13:规避短板”一节中介绍的可重用的基类ProcRuner,其构造函数就声明为protected。代码如下。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/32_02.jpg?sign=1739583109-LLiKmTjZ8qokhPiH5uKYW8UshYG2p0v8-0-de556d5d6f8a78aa7ea19d376fcfe4ff)
因为该类必须经过派生后才能复用,派生类必须重写两个虚函数。代码如下。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/32_03.jpg?sign=1739583109-TNEf4nUo8hcJRneSoyADiS6gbgNjWQrC-0-5013392dec35948faf9d5bfdaab62c6c)
如果一个类的构造函数被声明为private,那说明它既不能被派生,也不能被直接实例化,例如“法则 31:灵活注入对象”中的ModuleMgr类。它很可能为单例(singleton)模式,同时会提供一个配套的静态成员函数GetInst以供获取该类的全局唯一实例。代码如下。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/32_04.jpg?sign=1739583109-LiZvqIpbwUR3SEC2Vf5oRaMXrXBXWLj7-0-2dcf0a1237402ab7ef560811b93de7e1)
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_01.jpg?sign=1739583109-thpkTfkWfjS9ykRVBA6bnERsXp5UA84S-0-7dc799008314d74d746f6b25be832951)
● 公有继承
此外对于继承体系,也有其内在的表达。公有继承传递的信息是“is-a”的关系。比如派生类Eagle公有继承自基类Bird,C++语言中使用public关键字表示公有继承。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_02.jpg?sign=1739583109-X6m7Q2HpT81IDtxsq37QWmJiBcQVcBwR-0-9750589b3f6d5c0e9dc505407deafb7b)
对于Java语言,使用extends表示公有继承。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_03.jpg?sign=1739583109-zkluWSvY8HVQs3GtXZMKGwjvHAL4DDqz-0-fab10cfaae63a919594d57a722bbd97f)
这是面向对象设计中非常重要的一个概念。比如上面代码描述的信息是:鹰“是一种”鸟类。当然,读到这里希望读者不要浅尝辄止,有两个要点需要特别注意。
第一:对“是”字的理解要正确,“是”字不能理解为“等于”。比如下面这个派生关系。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_04.jpg?sign=1739583109-Qg5VaraW2K29XjQvhkZ65pyKoouiQGoo-0-5d8db667d6e2b37534269f384ad1e629)
马和牛都派生自动物类,曾经有人热烈地讨论过CompareAnimal函数,如下所示。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_05.jpg?sign=1739583109-zFpxyOONRzQGYz81yrb3RoVTfCd9AXAL-0-a4f57eac28c1110cf0b7f6cb8daa66d9)
当传入一个Horse和一个Bull的对象时,该CompareAnimal又该如何实现?
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_06.jpg?sign=1739583109-MVpmh5zMPwSmatkOJV6bxQBZ5oybNcto-0-65d5391cb839899df2aa8291a5f22001)
比体重还是比身高呢?因为马和牛没有可比性,因此这样的比较没有意义。但是他们简单地认为马“等于”动物,牛“等于”动物,既然都相等,就应该能拿来进行比较。
正确的理解应该为:“是”指的是一种被包含关系,是“属于”的意思,即前者“属于”后者的一类。比如我们所描述的学生是人、小轿车是车,都是指前者属于后者。即:学生属于人、小轿车属于车。
第二:公有继承的派生类和基类必须满足里氏替换原则。即所有使用基类对象的地方,都可以使用派生类对象进行替换。比如下面Android框架中,自定义的MyActivity派生自Activity。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/34_01.jpg?sign=1739583109-GVzR9QVPF5xKZ39ufT05LKQYsZMaeDhu-0-8e850fa1efc62ea730985ccee1dbc029)
系统启动时重写的onCreate方法会被回调,就是因为满足了这一原则。框架代码中操作的是Activity类,但传入的实际上是MyActivity的对象。以下代码描述了主要调用关系,剔除了无关代码。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/34_02.jpg?sign=1739583109-JxwVNZULjKr5lpwzAJwIVS7R4F80ezz7-0-bbe0528ce20fa84992cebe44956a81b7)
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/35_01.jpg?sign=1739583109-pUdpqs5bcodr9klm6yaK5UWkN54a8guT-0-3b58159db0affd107a1807e06bba011e)
当然,如果不满足里氏替换原则,则不能使用公有继承。比如下面Penguin(企鹅)类的派生关系。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/35_02.jpg?sign=1739583109-G375J89zVAejNKQ7TgWLL0YllmUwMIey-0-e8f0cd1d5979b882a773a0effe2a578a)
因为企鹅不会飞,因此不能给Penguin类定义fly函数。由于作用于Bird类上的fly函数不能作用于Penguin类,不满足里氏替换原则,因此不能使用公有继承描述二者的关系。
需要特别注意的是,一些在现实中看似合理的派生关系,用代码描述不一定合理。在“法则 25:正确理解面向对象设计”一节中将对这个话题展开进行讨论。