设计模式之设计模式原则

设计模式之禅观后感

单一职责原则(Single Responsibility Principle,SRP)

单一职责原则的英文名称是Single Responsibility Principle,简称是SRP。SRP的原话解释是:

There should never be more than one reason for a class to change.

单一职责原则的定义是:应该有且仅有一个原因引起类的变更。

单一职责的优点:
● 类的复杂性降低,实现什么职责都有清晰明确的定义;
● 可读性提高,复杂性降低,那当然可读性提高了;
● 可维护性提高,可读性提高,那当然更容易维护了;
● 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。

单一职责原则很难在项目中得到体现,非常难,为什么?在国内,技术人员的地位和话语权都比较低,因此在项目中需要考虑环境,考虑工作量,考虑人员的技术水平,考虑硬件的资源情况,等等,最终妥协的结果是经常违背单一职责原则。而且,我们中华文明就有很多属于混合型的产物,比如筷子,我们可以把筷子当做刀来使用,分割食物;还可以当叉使用,把食物从盘子中移动到口中。而在西方的文化中,刀就是刀,叉就是叉,你去吃西餐的时候这两样肯定都是有的,刀就是切割食物,叉就是固定食物或者移动食物,分工很明晰。这种文化的差异很难一步改造过来,但是我相信随着技术的深入,单一职责原则必然会深入到项目的设计中,而且这个原则是那么的简单,简单得不需要我们更加深入地思考,单从字面上大家都应该知道是什么意思,单一职责嘛!

最佳实践

对于接口,我们在设计的时候一定要做到单一,但是对于实现类就需要多方面考虑了。生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦,而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类,然后再使用聚合或组合的方式耦合在一起,人为制造了系统的复杂性。所以原则是死的,人是活的,这句话很有道理。单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。

里氏替换原则(Liskov Substitution Principle,LSP)

在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:

  • 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
  • 提高代码的重用性;
  • 子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;
  • 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;
  • 提高产品或项目的开放性。

继承的缺点如下:

  • 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
  • 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
  • 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。
    Java使用extends关键字来实现继承,它采用了单一继承的规则。从整体上来看,利大于弊,怎么才能让“利”的因素发挥最大的作用,同时减少“弊”带来的麻烦呢?

解决方案是引入里氏替换原则,什么是里氏替换原则呢?它有两种定义:

  1. 第一种定义,也是最正宗的定义:

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.

如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
2. 第二种定义:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

所有引用基类的地方必须能透明地使用其子类的对象。第二个定义是最清晰明确的,通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。

里氏替换原则为良好的继承定义了一个规范,一句简单的定义包含了4层含义。

  1. 子类必须完全实现父类的方法
  2. 子类可以有自己的个性
  3. 覆盖或实现父类的方法时输入参数可以被放大
  4. 覆写或实现父类的方法时输出结果可以被缩小

最佳实践

在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。

依赖倒置原则(Dependence Inversion Principle,DIP)

依赖倒置原则的原始定义是:

High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

翻译过来,包含三层含义:

  • 高层模块不应该依赖低层模块,两者都应该依赖其抽象;
  • 抽象不应该依赖细节;
  • 细节应该依赖抽象。
    在Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类。

依赖倒置原则在Java语言中的表现就是:

  • 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
  • 接口或抽象类不依赖于实现类;
  • 实现类依赖接口或抽象类。
  • 更加精简的定义就是“面向接口编程”

依赖的三种写法

  • 构造函数传递依赖对象
  • Setter方法传递依赖对象
  • 接口声明依赖对象

最佳实践

  1. 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
  2. 变量的表面类型尽量是接口或者是抽象类
  3. 任何类都不应该从具体类派生
  4. 尽量不要覆写基类的方法
  5. 结合里氏替换原则使用

接口隔离原则(Interface Segregation Principle,ISP)

接口隔离原则是对接口进行规范约束,其包含以下4层含义:

  1. 接口要尽量小
    此处举个栗子, 挂电话有两种方 式:一种是正常的电话挂断,一种是电话异常挂机,比如突然没电了,通信当然就断了。这两种方式的处理应该是不同的,为什么呢?正常挂电话,对方接受到挂机信号,计费系统也 就停止计费了,那手机没电了这种方式就不同了,它是信号丢失了,中继服务器检查到了, 然后通知计费系统停止计费,否则你的费用不是要疯狂地增长了吗?
    思考到这里,我们是不是就要动手把接口拆封成两个,一个接口是负责连接,一个接口是负责挂电话?是要这样做吗?且慢,让我们再思考一下,如果拆分了,那就不符合单一职责原则了,因为从业务逻辑上来讲,通信的建立和关闭已经是最小的业务单位了,再细分下去就是对业务或是协议(其他业务逻辑)的拆分了。想想看,一个电话要关心协议,要考虑中继服务器,等等,这个电话还怎么设计得出来呢?从业务层次来看,这样的设计就是一个失败的设计。一个原则要拆,一个原则又不要拆,那该怎么办? 好办,根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
  2. 接口要高内聚
    什么是高内聚?高内聚就是提高接口、类、模块的处理能力,减少对外的交互。
  3. 定制服务
    定制服务就是单独为一个个体提供优良 的服务。我们在做系统设计时也需要考虑对系统之间或模块之间的接口采用定制服务。采用 定制服务就必然有一个要求:只提供访问者需要的方法
  4. 接口设计是有限度的
    接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构 的复杂化,开发难度增加,可维护性降低,这不是一个项目或产品所期望看到的,所以接口 设计一定要注意适度,这个“度”如何来判断呢?根据经验和常识判断,没有一个固化或可测 量的标准。

最佳实践

接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原 子类来组装。但是,这个原子该怎么划分是设计模式中的一大难题,在实践中可以根据以下 几个规则来衡量:

  1. 一个接口只服务于一个子模块或业务逻辑;
  2. 通过业务逻辑压缩接口中的public方法,接口时常去回顾
  3. 已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化 处理;
  4. 了解环境,拒绝盲从。

迪米特法则的定义(Law of Demeter,LoD)

最少知识原则(Least Knowledge Principle,LKP)

迪米特法则也称为最少知识原则,虽然名字不同,但描述的是同一个规则:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调 用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心
迪米特法则对类的低耦合提出了明确的要求,其包含以下4层含义。

  1. 只和朋友交流
    迪米特法则还有一个英文解释是:Only talk to your immediate friends(只与直接的朋友通 信。)
  2. 朋友间也是有距离的
    一个类只和朋友交流,不与陌生类交流,不要出现getA().getB().getC().getD()这种情况(在一种极端的情况下允许出现这种访问,即每一个点号后面的返回类型都相同),类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在 的对象,当然,JDK API提供的类除外。
  3. 是自己的就是自己的
    如果一个方法放在本类中,既不增加类间关 系,也对本类不产生负面影响,那就放置在本类中。
  4. 谨慎使用Serializable

最佳实践

迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维 护带来了难度。读者在采用迪米特法则时需要反复权衡,既做到让结构清晰,又做到高内聚低耦合。
不知道大家有没有听过这样一个理论:“任何两个素不相识的人中间最多只隔着6个人, 即只通过6个人就可以将他们联系在一起”,这就是著名的“六度分隔理论”。如果将这个理论 应用到我们的项目中,也就是说,我和我要调用的类之间最多有6次传递。呵呵,这只能让 大家当个乐子来看,在实际应用中,如果一个类跳转两次以上才能访问到另一个类,就需要 想办法进行重构了,为什么是两次以上呢?因为一个系统的成功不仅仅是一个标准或是原则 就能够决定的,有非常多的外在因素决定,跳转次数越多,系统越复杂,维护就越困难,所 以只要跳转不超过两次都是可以忍受的,这需要具体问题具体分析

开闭原则(Open Closed Principle,OCP)

开闭原则是Java世界里最基础的设计 原则,它指导我们如何建立一个稳定的、灵活的系统,先来看开闭原则的定义:

Software entities like classes,modules and functions should be open for extension but closed for modifications.

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。并不意味着不做任何修改,低层模块的变更,必然要有高层模块进行耦合,否则就是一个孤立无意义的代码片段。

开闭原则的重要性

  1. 开闭原则可以提高复用性
    在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立 实现一个业务逻辑。只有这样代码才可以复用,粒度越小,被复用的可能性就越大。那为什 么要复用呢?减少代码量,避免相同的逻辑分散在多个角落,避免日后的维护人员为了修改 一个微小的缺陷或增加新功能而要在整个项目中到处查找相关的代码,然后发出对开发人 员“极度失望”的感慨。那怎么才能提高复用率呢?缩小逻辑粒度,直到一个逻辑不可再拆分 为止。
  2. 开闭原则可以提高可维护性
    一款软件投产后,维护人员的工作不仅仅是对数据进行维护,还可能要对程序进行扩 展,维护人员最乐意做的事情就是扩展一个类,而不是修改一个类
  3. 面向对象开发的要求

最佳实践

开闭原则是一个终极目标,任何人包括大师级人物都无法百分之百做到,但朝这个方向 努力,可以非常显著地改善一个系统的架构,真正做到“拥抱变化”。

总结

软件设计最大的难题就是应对需求的变化,但是纷繁复杂的需求变化又是不可预料的。 我们要为不可预料的事情做好准备,这本身就是一件非常痛苦的事情,但是大师们还是给我 们提出了非常好的6大设计原则以及23个设计模式来“封装”未来的变化,我们在前5章中讲过 如下设计原则。

  • Single Responsibility Principle:单一职责原则
  • Open Closed Principle:开闭原则
  • Liskov Substitution Principle:里氏替换原则
  • Law of Demeter:迪米特法则
  • Interface Segregation Principle:接口隔离原则
  • Dependence Inversion Principle:依赖倒置原则

把这6个原则的首字母(里氏替换原则和迪米特法则的首字母重复,只取一个)联合起 来就是SOLID(solid,稳定的),其代表的含义也就是把这6个原则结合使用的好处:建立 稳定、灵活、健壮的设计。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值