一、设计原则概述
古人云: 有道无术,术可求.有术无道,止于术.
而设计模式通常需要遵循一些设计原则,在设计原则的基础之上衍生出了各种各样的设计模式。设计原则是设计要求,设计模式是设计方案,使用设计模式的代码则是具体的实现。
设计模式中主要有六大设计原则,简称为SOLID ,是由于各个原则的首字母简称合并的来(两个L算一个,solid 稳定的),六大设计原则分别如下:
1、单一职责原则(Single Responsibitity Principle)
2、开放封闭原则(Open Close Principle)
3、里氏替换原则(Liskov Substitution Principle)
4、接口分离原则(Interface Segregation Principle)
5、依赖倒置原则(Dependence Inversion Principle)
6、迪米特法则(Law Of Demter)
软件开发中我们要基于这六个原则,设计建立稳定、灵活、健壮的程序.
二、单一职责原则
2.1 官方定义
单一职责原则,英文缩写SRP,全称 Single Responsibility Principle。
在<<架构整洁之道>>一书中 关于这个原则的英文描述是这样的:A class or module should have a single responsibility 。如果我们把它翻译成中文,那就是:一个类或者模块只负责完成一个职责(或者功能)。
2.2 通俗解释
单一职责原则的定义描述非常简单,也不难理解。一个类只负责完成一个职责或者功能。
也就是说在类的设计中 我们不要设计大而全的类,而是要设计粒度小、功能单一的类.
比如 我们设计一个类里面既包含了用户的一些操作,又包含了支付的一些操作,那这个类的职责就不够单一,应该将该类进行拆分,拆分成多个功能更加单一的,粒度更细的类.
2.3 场景示例
那么该如何判断一个类的职责是否单一 ?
其实在软件设计中,要真正用好单一职责原则并不简单,因为遵循这一原则最关键的地方在于职责的划分,而职责的划分是根据需求定的,同一个类(接口)的设计,在不同的需求里面,可能职责的划分并不一样.
我们来看下面这个例子:
在一个社交媒体产品中,我们使用UserInfo去记录用户的信息,包括如下的属性.
请问上面的UserInfo类是否满足单一职责原则呢 ?
- 观点1: 满足,因为记录的都是跟用户相关的信息
- 观点2: 不满足,因为地址信息应该被拆分出来,单独放到地址表中保存.
正确答案: 根据实际业务场景选择是否拆分
- 该社交产品的有用户信息只是用来展示的,那么这个类这样设计就没有问题
- 假设后面这个社交产品又添加了电商模块, 那就需要将地址信息提取出来,单独设计一个类
总结: 不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的,最好的方式就是:
我们可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构
如何判断一个类的职责是否单一?
这里没有一个具体的金科玉律,但从实际代码开发经验上,有一些可执行性的侧面判断指标,可供参考:
- 类中的代码行数、函数、或者属性过多;
- 类依赖的其他类过多
- 私有方法过多
- 类中大量的方法都是集中操作类中的几个属性
三、开闭原则
3.1 官方定义
一般认为最早提出开闭原则(Open-Close Principle,OCP)的是伯特兰·迈耶。他在1988 年发表的《面向对象软件构造》中给出的。在面向对象编程领域中,
开闭原则规定软件中的对象、类、模块和函数对扩展应该是开放的,但对于修改是封闭的。这意味着应该用抽象定义结构,用具体实现扩展细节,以此确保软件系统开发和维护过程的可靠性。
3.2 通俗解释
定义:对扩展开放,对修改关闭
对扩展开放和对修改关闭表示当一个类或一个方法有新需求或者需求发生改变时应该采用扩展的方式而不应该采用修改原有逻辑的方式来实现。因为扩展了新的逻辑如果有问题只会影响新的业务,不会影响老业务;而如果采用修改的方式,很有可能就会影响到老业务受影响。
开闭原则是所有设计模式的最核心目标,也是最难实现的目标,但是所有的软件设计模式都应该以开闭原则当作标准,才能使软件更加的稳定和健壮。
优点:
- 新老逻辑解耦,需求发生改变不会影响老业务的逻辑
- 改动成本最小,只需要追加新逻辑,不需要改的老逻辑
- 提供代码的稳定性和可扩展性
3.3 场景示例
系统A与系统B之间进行数据传输使用的是427版本的协议,一年以后对427版本的协议进行了修正。
设计时应该考虑的数据传输协议的可变性,抽象出具有报文解译、编制、校验等所有版本协议使用的通用方法,调用方针对接口进行编程即可,如上述示例设计类图如下
调用方依赖于报文接口,报文接口是稳定的,而不针对具体的427协议或42