单一职责原则
这点是整个系统结构清晰的基础,即使用这样一个标准来检验每个类或包:是否拥有一条以上职责?是则认为设计失败。

举个例子:现有一个日志记录类 LogWriter,日志记录异常信息是常有的操作,是否应该设计一个 LogWriter->log(Exception $ex); 然后在 log 方法内部获取 $ex 的异常信息写入日志呢?这样看上去用起来很方便,但是是一个不佳设计。因为 LogWriter 拥有了两个职责:异常处理和日志记录。推荐的方式是 LogWriter->log(string $message); 只接受简单“需要日志记录的信息”,因为这才是 LogWriter 应该拥有的职责——记录日志。我们看下,如何检验“是否属于单一指责”?或许有一个简单的检验方法:异常虽然是常见的日志信息,但不是全部。所以异常和日志无法构成“包含”的关系;而日期、级别这些,是和“日志”的“核心职责”相关的,所以属于 LogWriter 可以涉及的信息。浓缩成一句话:对于 LogWriter 来说,Exception 是可剥离的,而日志日期、日志级别是不可剥离的。我们设计单一职责的组件,应该最大限度的剥离。

里氏代换原则
里氏代换原则有严格定义,可以自己搜索一下。这里只是从我个人的理解,通俗的叙述。

用一句很通俗很实际的话来概括里氏代换原则:保持子类不会破坏父类的业务逻辑完整性。

面向对象的系统中,通过继承来扩展的子类,往往可以继承父类的方法,这种继承可以一定程度上构成对父类的修改。尤其是 Java、PHP 的继承模型,所有方法默认都为虚方法(Virtual),所以子类可以完全覆盖父类方法。这种覆盖一定要特别谨慎,因为可能会破坏父类业务逻辑的完整性。根据“面向接口而非面向实现”的思想,调用一个子类时很可能根本不了解子类实现,而是完全按照父类的定义。这时子类如果不符合里氏代换原则(一个“坏”继承),就会带来意想不到的结果和隐匿的错误(Bug)。运气好可能会导致无法运行,运气不好可能运行起来完全没有问题,但是带来错误的逻辑结果(这点是大忌)。

所以在面对继承时候,要时刻记住:子类只能扩展(extend)父类的方法,不能破坏。这点在面对“胖接口”(见下“接口隔离原则”)的时候尤其容易犯错,比如写“哑方法”(即父类的方法在子类的特例情况下没有意义,就把这个方法继承复写成一个空方法)

依赖倒置原则
这点是 Java 神框架 Spring 的核心设计——依赖注入的理论基础。

即系统中上层组件(如领域模型)和底层组件(如数据访问层)之间的依赖关系,不应该表现为上层组件直接依赖底层组件,而应该把底层组件抽象出来(通过接口或抽象类)。

这个咋听起来好像吃饱了没事干,但是其实细想一下,抽象出来和不抽象出来是有本质区别的。原来的依赖关系是【上层组件】依赖【底层组件】,而抽象出来之后就变成了【上层组件】依赖【抽象层】,【底层组件】也依赖【抽象层】。上层和底层的依赖通过一个抽象层的介入,依赖关系被变相解开了,就如其名——依赖倒置。

所以这是大型的系统中解除组件耦合的重要方式。依赖注入(控制反转)就是这一原则的具体实现。

接口隔离原则
我觉得这个应该是最好理解的一条设计原则,就是说不要设计“胖接口”。接口和抽象类不同,一个类只能继承于一个父类但可以实现无数个接口。所以接口的设计应该满足“越小越好”的原则。太“胖”的接口容易让客户端编写哑方法。

举个例子:有一个数据实体类(就是可以和数据库交互的数据容器),因为能被缓存,所以必须拥有一个序列化成字符串的方法 serialToString(),又能和数据库交互作用,所以必须拥有 load() save() 两个方法。所以我是不是应该订下契约——所有实现了包含三个方法的 IDbEntity 接口可以作为实体访问呢?这个包含 serialToString() load() save() 三个方法定义的接口 IDbEntity 其实就是一个违反接口隔离原则的胖接口。更好的做法是把它拆分成两个接口:包含 serialToString() 的 ISerial 和 包含 load() save() 的 IDbAccess ,这样如果我们遇到一个“既需要访问数据库,有能被序列化”的数据实体,就不必实现一个哑方法 function serialToString() { return null; } 了。同时也更好做类型约束,例如没有实现 ISerial 的类就能被 ($obj instanceof ISerial)检测为 false。

这个其实就是面对接口(Interface)和抽象类(Abstract Class)区别的问题。区别在于抽象类面对的是继承,是单线的(不考虑 C++ 、Python 的多重继承这类非主流问题);而接口面对的是“实现”,是多线的。根本原因在于设计继承模型的时候,“继承”是设计给 is-a 关系的,Administrator is a person,所以 Administrator 可以继承 Person;而接口是设计给 has-a-feature 关系的,数组拥有被迭代器遍历的功能,拥有被下标访问的功能,所以 ArrayObject 对象实现 Iterator 和 ArrayAccess 两个接口。

貌似例子举的很烂,如果有好的例子烦请推荐。

最少获知原则
这个也叫迪米特法则,笼统来说就是“面向接口忘记实现”。设计的时候,一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。

迪米特法则也有严格定义,而且有狭义和广义两种。请自己谷歌之。

开放闭合原则
这点是关于系统扩展的。需求会不断变化,系统也难免要扩展。而我们设计的目标,是在系统扩展的时候,只“扩充”而不“修改”原来的系统。即“面对扩充开放,面对修改闭合”

程序本天成,妙手偶得之!我们只是代码的搬运工!

转载请注明:http://www.521php.com/archives/345/

发表评论

昵称:

网址:

eg.博客主题调用的是Gravatar头像,你可以通过邮箱注册获得头像.
/ 快捷键:Ctrl+Enter