设计模式---单一职责原则、开放封闭原则、依赖倒转原则

        今天工作之余抽空翻了翻大话设计模式第3章到第5章,主要讲了三个设计原则:单一职责原则、开放--封闭原则、依赖倒转原则(依赖倒转原则中还讲了里氏代换原则)。在此做一下笔记,以方便自己忘记的时候回头看一下。

        

一、单一职责原则

        什么是单一职责原则呢?举个简单的例子,我们生活中经常用到手机,现在的手机基本都会包括音乐播放功能、拍照功能、摄像功能、打游戏等各种功能,用面向对象的编程眼光来看,手机是一个高聚合的类,里边包含各种各样的功能。而像MP3只有专一的音乐播放功能、摄像机只有拍摄功能、游戏机只有打游戏功能等,这种只有专一的功能,在面向对象的编程中就被成为单一职责原则

        再举个例子,假设要开发一个俄罗斯方块小游戏,用编程手机这样类的方式会怎么做呢?首先需要一个窗口显示方法,还需要旋转、下落、移动、碰撞方法,所有的方法都写到一个类中进行俄罗斯方块的开发,这种设计导致在手机上开发的游戏代码,无法拿到电脑上来用,而同一个游戏像旋转、下落、移动、碰撞这些逻辑方法是相同的。所以我们应该将逻辑方法和界面分割开来,使得同一个逻辑代码换到不同端上仍旧能够使用。如果一个类承担的职责过多,就等于把这些职责耦合到了一起,一个职责的变化可能削弱或者抑制这个类完成其他职责。这种耦合会导致脆弱的设计,当变化发生的时候,设计会遭到意想不到的破坏。界面的变化和游戏本身没有关系,界面是容易变化的,游戏逻辑是不容易变化的,将他们分离开来,有利于界面的改动。软件设计真正要做的许多内容,就是发现职责并将这些职责分离开来,如何去判断是否要分离?如果你能想到多于一个动机去改变类,那么这个类就具有多于一个的职责。

        

二、开放--封闭原则

        开放--封闭原则指的是:对于扩展是开放的,对于更改是封闭的。设计软件有容易维护有不容易出问题的最好方法是多扩展,少修改。举个例子,公司上班老员工总是迟到怎么办?可能会有人提议:严格执行考勤制度,迟到扣钱!但是工作的目的是什么?每天保证8小时工作,或者说最主要的目的是完成业绩目标,所以可以设置弹性上班制度、或者规定每月可以迟到的次数,甚至对市场销售人员来说更加可以以业绩为标准。时间不固定了,这就是对时间和业绩的修改关闭,对时间制度扩展的开放。

        所以我们在程序的设计阶段就应该尽可能的考虑周全,保证一个类设计的足够好,写好之后就不用在修改了,但是绝对的封闭是不可能的。无论模块是多么的封闭,都会存在一个无法对之封闭的变化。既然不可能完全封闭,设计人员对他设计的模块应该对哪种变化封闭做出选择,他必须猜测出最有可能发生的变化种类,然后构造抽象来隔离那些变化。(不可能全部猜测到,等到遇到发生变化的点,及时进行抽象,发现的越晚,要创建正确的抽象就越困难。)。我们要做到面对需求的时候,对程序的改动是增加新代码来完成的,而不是更改现有的代码。

设计模式---单一职责原则、开放封闭原则、依赖倒转原则
图1 大话设计模式P36

        如图,客户端在做一个计算器的时候,刚开始只有加法运算,但是在实际做的时候,有加了减法运算以及乘法和除法运算,在这里及时发现了变化的点,所以抽象出算法类父类,方便扩展。封闭已有的程序,开放扩展。开发--封闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是易维护、易扩展、易复用、灵活性高。程序员应该对程序中呈现出频繁变化的那些部分做出抽象,对程序中的每个部分做出抽象不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。

        

三、依赖倒转原则

        我们日常使用的电脑就是一个好的面向对象设计,假设内存条坏了,我们只需要更换一个内存条即可继续使用,主板换了,更换主板等,笔记本是一个典型的面向接口设计,CUP有许多复杂的设计功能,但是在使用的时候,只需要通过阵脚连接到主板上便可以正常工作,主板完全不了解CPU,只需要预留CPU的接口即可。抽象不应该依赖细节,细节应该依赖于抽象,说白了就是面对接口编程,不要对实现编程。主板、CPU、内存条、硬盘都是针对接口进行设计的,如果针对实现来编程,那么内存就要针对不同的主板就行设计,就会出现主板坏了要换另一个牌子的主板时,内存条等也需要更换的尴尬了。

        依赖倒转原则:1、高层模块不应该依赖低层模块,来个都应该依赖抽象;2、抽象不应该依赖细节,细节应该依赖抽象。举个例子:公司准备做一套新项目,发现和某个项目高层模块是相同的,但是高层模块都依赖于底层模块,底层模块又和数据库要绑定,书新项目开发却和这个项目数据库不同,这就面临了更换主板却要连内存条等要一并更换的问题了,所以高层模块和低层模块不相互依赖,他们都依赖于抽象,只要接口是稳定的,任何一个更换都不必担心了。

        里氏代换原则:如果一个软件实体使用的是一个父类的话,那么一定适用于其子类,而且他察觉不出来父类和子类的区别。也就是说,在软件里,把父类替换成子类,程序的行为没有变化。LSP:子类型必须能够替换掉它们的父类型。举个例子,鸟能飞,企鹅能继承鸟这个父类吗?虽说企鹅是鸟,但是在程序中因为企鹅不能飞,代替不了鸟这个父类,所以企鹅不能继承鸟这个父类。只有当子类可以替换掉父类,软件运行不受影响的时候,父类才能真正的被复用,而子类也能在父类的基础上增加新的行为。

设计模式---单一职责原则、开放封闭原则、依赖倒转原则

图2:动物父类及其子类以及扩展(大话设计模式P42)

        笔记本是典型的面向接口设计,而收音机是典型的耦合过度设计。依赖倒转其实可以说是面向对象设计的标志用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序的所有的依赖关系都是终止于抽象类或者接口,那就是面向对象设计,反之就是过程化设计了。