一. 类的接口应该展现一致的抽象层次
展现一致的抽象层次,就是说与当前接口抽象概念无关的函数不要放到接口中。比如一个雇员的类接口,却提供了查询天气的函数,一定要从抽象概念角度决定函数的留存。这也同时提高了内聚性,符合职责单一原则。不相关的函数应该继续抽象,分成若干个接口。
二. 提供成对的服务
这一点还是比较常见的,也容易做到。比如有一个Init的初始化,就应该有一个UnInit做反初始化,但也要看需求是否需要一个相反的服务。
三. 尽可能地限制类成员的可访问性
这其实是保证类的语法封装性。类的使用者知道得越少,出问题的的可能就越小。在我的开发经历中,经常为了省事儿,直接把数据成员声明成public,方便访问,这样确实很不好,应该进行访问控制。
四. 不要对类的使用者作出任何假设,除非在接口设计中有过明确说明
从接口函数的参数来看,接口开发者不应该假定传入的参数合法性及合理性。此外,从调用时序来看,一般不应该假定接口调用者在调用当前函数之前已经进行其他的依赖调用。
五. 让阅读代码比编写代码更方便
阅读代码的次数要远比编写代码的次数多,有过大型项目开发经历的人应该深有体会,且不论同一份的代码需要频繁重构和优化,而且在项目人员变动较频繁的情况下,代码的可读性就更加重要了。一份连神都难看懂的代码会让接手的同事抱怨不停。
六. 要特别警惕从语义上破坏封装性
封装性可以分为两个维度:语法封装和语义封装。语法封装一般比较容易办到,不该暴漏出去的不暴露,极致情况下,所以数据成员应该都是私有的(保护的情况下,对子类依然是暴露的),通过函数进行访问控制。语义的封装,就是接口的使用者除了接口本身的释义之外,不应该去了解接口的实现细节,并假定这些细节一定会执行。比如:
1. 不去调用A类的InitializeOperations()函数,因为你知道A类的PerformFirstOperation()函数会自动调用它;
2. 不在调用employee.Retrive(database)之前去调用database.Connet()函数,因为你知道未建立数据库连接的时候,employee.Retrieve()会去连接数据库;
3. 不去调用A类的Terminate()函数,因为你知道A类的PerformFinalOperation()子程序已经调用过它;
4. 即便在objectA离开作用域之后,你仍然去使用由objectA创建的指向objectB的指针或者引用,因为你知道objectA把objectB放置在静态存储空间中了,所以objectB肯定还能用;
5. 使用ClassB.MAXINUM_ELEMENTS而不是用ClassA.MAXINUM_ELEMENTS,因为你知道它们两个的值是相等的。
以上这些例子的问题都在于,它们让调用代码不是依赖于类的公共接口,而是依赖于类的私有实现。这不是针对接口编程,而是透过接口针对内部实现编程,语义封装性就被破坏了,类的抽象能力也就遭殃了。这个在自己写的一些模块中尤其要警惕,因为自己是很清楚一个类的实现细节的,所以就很容易在使用此类的时候破坏语义的封装。应该做到“除了接口,别无他想”。
写代码,应该如履寒冰!