对设计模式的总结之原则
前言
面向对象编程已经提出无数年了,现今已经成了研发们的必备之技。各大小面试,技术面试官都会让你谈谈面向对象相关的知识:你对相面对象了解多少?谈谈你对面向对象的理解。不管是什么应用层面的语言(C++,JAVA,C#),面向对象设计思想永远是基础。玩不转面向对象,当个小鸟都难。
本博文系列记录了我对相面对象编程思想的应用-设计模式的总结,当前博文记录了我对设计模式遵循原则的理解。在讲解之前,推荐一篇设计模式的基础文章:GRASP (职责分配原则)。
概要
所谓前人栽树后人乘凉,软件行业也是这个理。在面向对象编程思想还没出来的时候,研发沿用面向过程开发,建造了无数想着都吓人的怪物项目。现如今许多银行或大型公众机构的系统还是用C语言或者C++写的,无数次代码迭代,维护难度已经非常巨大。为了避免后期维护难度加大,不易扩展的问题,面向对象编程思想应运而生。再经过前人多年的摸索和总结,设计模式的6大经典原则出现了:单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口隔离原则、迪米特法则。玩转这6大原则,是良好的完成一个项目的基本保证,至于OO的6大原则是不是有不合理之处,就是大大牛去考虑的了。
设计模式链接
原则
单一职责原则
如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。
就像社保局办理业务的窗口,如果没有分职责,每个窗口都可以办理挂失、缴费、异地转移、补办、密码重置等业务,当某需要增加一个新的业务或某一项业务需要更改时,整个社保局窗口需要停止所有业务办理,为新业务做准备。如果每个窗口只办理指定的功能相近的业务,就可以有效的避免之前的问题。
现在许多新的设计思路都是以这个原则为基础产生的,大到微服务治理,中到模块(插件话)开发,小到类功能分化。微服务可以使某个系统分开部署到不同的服务器上面,以增加系统的效率和减少版本升级难度;插件话开发,可以在用已开发好的模块+少量的后期开发组装成新的项目,当模块够多够丰富的时候,做项目就像玩积木,可以减少很大的开发成本;类的功能分化,可以避免因为某个类出现问题,影响当前类里面的其他功能。再者,类功能不清晰,后期维护可读性大大降低,增加了维护成本。
在类层面怎么做啦?项目前期想完全确定各种职责是完全不可能的,随着迭代次数增多,某些单一职责可能到了就会出现多个不确定的分支功能。所以,在项目前期借助UML工具画出示例图和类图,尽量找出各种职责的区别。在后期迭代中出现职责分支,就重新建类做分支功能,确保单个类的职责唯一。
注:个人觉得某些可控的,功能类似的职责就不需要进一步细分了,代码粒度过细也会影响后期系统的维护成本。
开放封闭原则
软件设计本身所追求的目标就是封装变化、降低耦合,而开放封闭原则正是对这一目标的最直接体现。 开放封闭原则主要体现为:对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况;对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改。
怎么应用啦?这个就需要用单一职责原则做基础,加以抽象实现。单一职责原则确保每次变更业务的时候,不回去修改以前的功能。而抽象是将业务功能抽象为接口,当业务依赖于固定的抽象时,对于修改就是封闭的;而通过继承和多态机制,从抽象体派生出新的扩展实现,就是对扩展的开放。就比如工厂计算工资,厂长,主管,组长和员工的计算算法是不同的,如果将多种工种的工资计算放入一个类里面绝对是不合理的。使用开放封闭原则+单一职责原则,将工厂计算工资作为固定的抽象类,将计算厂长,主管,组长和员工工资这些可能会有变化的算法封闭继承计算工资的抽象类。如果后期需要添加线长的工资计算,直接在抽象类基础上派生一个线长工资计算就行了。
注:开放封闭追求目标就是封装变化、降低耦合,也就是说对于经常变化的业务才能够用抽象加以封装,对业务固定的没有必要这么多。
里氏替换原则
子类型必须能够替代他们的基本类型。这就是鼓励使用继承,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
协变和逆变就是用到这个原则,使得一个类可以安全的隐式转换成另一个类。
里氏原则很好理解,但是去做好这个继承啦?在真正的开发环境中不是继承越多越好,相反继承越多,越容易造成代码逻辑的混乱,而且增加了父类与子类的耦合性,怎么来把握这个度就尤为重要了。我个人认为继承层数最多3层,在新添加业务的时候,发现层数已经2层了,就想办法将新的业务与原业务类放在同一层,去继承一个新老业务都实用的基类。
依赖倒置原则
依赖倒置的两个规定就是:1、高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。2、抽象不应该依赖于具体,具体应该依赖于抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。在以前面向过程变成中,上层与下层是紧密结合的,当下层代码有大的变动时,上层代码也必须做相应的变化,这样子降低了代码的复用性,也会提高开发成本。而依赖倒置原则在上下层之间加了一层变化率较小的抽象,很好的解决了上下层紧密依赖的问题。问题又来了,如果逻辑变动过大,大到中间的抽象层都需要变动了,那怎么办?这就需要结合前面的单一职责原则,将职责细粒话,在代码前期就最大限度设计好抽象层代码。如果是在后期出现了底层大的改动,首先看能否直接改动抽象层与相应的上层代码,且保证不会出问题。如果无法保证,就建立新的抽象和下层代码,让需要的上层去调用新抽象层。
接口隔离原则
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。
接口隔离原则是基于单一职责原则来的,有了细粒度的职责分化,才会有接口隔离的产生。比如:一个游戏平台统一账户系统,当客户从不同的游戏入口登录或登出的时候,统一账户系统都会与不同的游戏服务端进行信息交互,在玩家游戏中,账户系统又会和不同的游戏服务端进行信息交互。为了后期扩展开发,登录,登出,玩家游戏实时信息处理这3个大功能就可以分成2个接口,而不是1个个接口,因为所有的游戏都会有登录、登出功能,但是不一定需要玩家游戏实时信息处理这个功能。
迪米特法则
一个对象应当对其他对象有尽可能少的了解。也就是说:如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
使用注意事项:在类的划分上,应当创建弱耦合的类,类与类之间的耦合越弱,就越有利于实现可复用的目标;在类的结构设计上,每个类都应该降低成员的访问权限;在类的设计上,只要有可能,一个类应当设计成不变的类;在对其他类的应用上,一个对象对其他类的对象的应用应该降到最低;尽量限制局部变量的有效范围,不要暴露类成员,而应该提供相应的访问器(属性)。
过度使用,会在在系统里造出大量的桥接式方法,这些方法仅仅是传递间接的调用,与系统的业务逻辑无关。直接导致系统更加复杂,模块间调用变得不协调。
总结
有人会问,是不是完完全全按照以上原则来设计、编码,最终完成的项目一定会很好?我个人认为,要是完完全全按照以上原则来干事,研发不死,项目也得死。什么事情都得有个度,灵活运用,大量的使用和测试经验才是真正的牛B。那么,设计模式和它遵循的原则就不顾了吗?不是的,原则的追求的就是让代码更加灵活,提高重新性、扩展性和维护性,这个和研发设计者的目的是一样的,只要更好的运用才能设计出更好的系统项目。怎么才能更好,我也在慢慢学习。