软件设计的原则
单一职责原则
单一职责原则(Single responsibility principle,SRP)初看起来是个很简单的一个原则,只做一件事嘛。但是,往往越是寻常普通的事物,追究起来,越是可能蕴含非比一般的意义。
事实上,软件设计出来是要解决具体问题的,而实际生活千千万万的事物,都不是那么简单可以用单一行为来模拟抽象的。比如,一个鸟儿会鸣叫,会喝水,会飞;一个手机可以打电话、玩游戏、聊天,上网查资料等等。在实际开发如何度量、划分职责,并不是一件随随便便的事情;
SRP原话是:
There should never be more than one reason for a class to change。
一个事物具备多个行为,将多个行为分别抽象成单独的行为接口,与将事物本身抽象成接口;前者更符合设计原则,但是后者往往是实际开发的选择。因为后者看起来似乎更简单一些,尤其是时间有限,技术有限的情况下。
但是长远来看,有时候前期偷懒的行为,经常会导致后期维护困难,甚至可能会面临改一个功能需要大范围修改代码的情况。所以,更应该将单一职责作为一种标准,做开发需要细细琢磨,将其深入到日常开发中。
里式替换原则
里式替换原则(Liskov Substitution principle,LSP)包含两个含义:
- 在T 出现的任何地方,都可以替换成S,且不会改变程序的行为;那么,可以认为S是类型T子类型;
- 所有使用基类的地方,都应该可以透明地替换成其子类型;
在使用面向对象的语言进行编程时,继承是一个非常重要的一部分;它具有不少优点:
- 可以将公共代码共享在基类,提高代码复用性
- 子类可以继承父类的对外接口,又可以做部分定制;提升了可扩展性
同时,它也具有一些缺点:
- 继承有一定的侵入性,派生类拥有基类的方法,且可以覆盖基类定义的行为;
- 在一定程度上,基类的存在就影响到了派生类的灵活性;
- 继承在某种程度上,增强了耦合性;派生类修改基类的属性或行为时,有可能会带来不好的结果;
比如,如果对于Phone这个基类,除了派生出在严肃场景下,可以正常使用的IPhone、Andrond、老式功能机等子类;也可以定义出小孩子的玩具手机,破坏原有的功能,比如通话聊天;
里式替换原则相当于做了一个规范,来约束继承的泛滥;
- 子类必须完全实现父类的方法,使用父类的地方,就可以使用子类;
- 子类可以有自己的特殊性;使用子类的地方,不需要可以替换成父类;
- 覆盖和实现父类的方法时,输入参数可以被放大;
- 复写或实现父类的方法时,输出结果可以被缩小;
依赖倒置原则
依赖倒置原则 (Dependence Inversion Principle,DIP)包含三个含义:
- 高层模块不应该依赖底层模块,两者都应该依赖抽象;
- 抽象不应该依赖细节
- 细节应该依赖抽象
这个比较好理解,尤其是现在Java项目基本都使用Spring开发,通过Ioc机制,基本都是面向接口的编程;
依赖倒置的三种实现方式:
- 通过构造函数
- 通过setter
- 通过接口方法声明
接口隔离原则
这里的接口不单单指的是Java中interface关键字声明的接口,也包括实例接口。对一个对象来说,它对应的类就是实例接口。接口隔离原则有两个定义:
- 客户端不应该依赖它不需要的接口上
- 类间的依赖关系应该建立在最小的接口上
依据这个职责,应该尽量细化接口,接口中的方法应该尽可能的少。
参照《设计模式之禅》,接口隔离的最佳实现:
- 一个接口应该只服务于一个子模块或者业务逻辑
- 接口不应该暴露过多的public方法
- 已经污染的接口,尽量修改;若变更风险较大,可以考虑适配器模式转化处理
- 了解环境,拒绝盲从;针对实际业务背景进行拆分,不要照搬照抄
迪米特法则(最少知识原则)
比较好记的还是最少知识原则,即一个类应该对它的依赖保持最少的了解,尽量降低类之间的耦合;
对于迪米特法则,有以下几点需要注意:
- 只和朋友交流,类不应该去了解依赖背后做的事情,而跟依赖对象依赖的第三方有交集
- 朋友之间也应该保持距离;尽可能约束类的对外、对子类的方法和属性;需要在访问权限上尽可能收窄
- 是自己的就是自己的;如果出现一个方法不知道放在哪个类中,那么可以这样考虑:如果一个方法放在本类中,既不增加类间关系,也不对类产生负面影响,那就可以放置在本类中
开闭原则
简单来说,就是对扩展开放,对修改关闭;理解起来就是,尽量不修改源码,依然可以增加新功能;
原文:
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则,指出当出现新的需求时,应该首先考虑通过扩展现有的接口,而不是修改现有的代码。对一个接口来说,如果在当前接口上新增一个方法表示新的行为的话,会导致所有现有的实现都跟着变。应当首先考虑扩展,比如创建继承老接口的新接口,新的行为只需要在新的实现类中添加’。
软件不是开发出来就可以一直保持不变,而是往往会发生变化。开闭原则也指导我们,要拥抱变化,在软件的设计之处,可以考虑下以后可能的变化,并为之提供便利。
以上是六大设计原则,但是有的书还会提到另外一个原则,合成复用原则;
合成复用原则
合成复用原则,尽量使用对象组合或对象聚合的方式来实现代码复用,而不是继承关系;这个也比较好了理解,也就是大家常说的,组合优于继承;