一、前言
不知道大家是否有这样的体会,就是在学习设计模式的时候,看了很多书籍,也照着很多示例把每个模式挨个敲了几遍,但过了一段时间后,就会忘了一大半。或者有的朋友尝试在业务编码中使用,却越用越复杂,本来一个类几个方法能搞定的业务,套用模式后会多出好多接口和类,所以用着用着就放弃了。我说的比较直接点,很多教材或博客中使用Animal、Fruit、Car这些例子来教设计模式,初衷是好的,但真没多大用,甚至会误人子弟。
最近笔者再次学习了设计模式(不知道是这些年的第多少次了),突然有了些感悟。我尽量用最简单通俗的语言描述出我想表达的,有的观点可能比较偏激或不太适合所有人,但如果大家能从这篇文章中GET到一两个点,那也值了,哈哈。
下面我先以我个人的想法简单粗暴地理解一下设计模式的六大原则。Show Time!
二、六大原则
2.1、单一职责原则
官方解释:单一职责原则(SRP:Single responsibility principle)又称单一功能原则,面向对象五个基本原则(SOLID)之一。它规定一个类应该只有一个发生变化的原因。
大白话:一个类只有一个发生变化的原因,我只能说在业务编码中不太可能,或很维做到。其实大家不必纠结于有几个让类发生变化的原因。个人建议你想实践单一职责原则,最好先养成良好的编码规范,如果你平时写代码,一个类里不管什么业务方法都往里塞,一个方法里嵌套着各种判断,再好的原则也帮不也你。
实践:和类名不相关的业务不要放在这个类里,和方法名不相关的代码请拆成单独的子方法。还有当你在定义一个接口、类或方法的时候,从业务的角度用心地多想一下:这段代码放这里真的合适吗?
2.2、里氏替换原则
官方解释:里氏替换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
大白话:这个原则其实还是很好理解的,主要是约束子类的行为,要求其行为和基类保持一致,如果替换为子类后,程序运行不正常,则说明子类没有按基类的预期实现业务,或者说子类不适合继承这个基类,对不起,请找适合的基类继承。
实践:如果子类从一个基类继承后,实现基类定义的虚方法时感觉很别扭痛苦的时候,请考虑一下是否一定要从这个基类继承。
2.3、依赖倒置原则
官方解释:依赖倒置原则(Dependence Inversion Principle)是程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。
大白话:多用接口和抽象类,少用实现类(工具类除外)。大家看框架源码的时候应该能感觉到,大神在实现框架的时候到处都是接口或抽象类,而自己的代码却是一大片的实现类,其实直接用实现类是没有问题的。架构方法中有一句叫可扩展性,其实接口和抽象类就是实现可扩展性的基石。当需要扩展原有逻辑的时候,别人用接口和抽象类的直接加个新子类继承下,你用实现类的到处一大片一大片的改,请回答我,有没有?
实践:写代码的时候多考虑扩展性,如果这段代码以后扩展或变化的可能性很高,请用接口或抽象类封装下,抽象出不变的接口,将变化的部分留给不同的实现类。
2.4、接口隔离原则
官方解释:接口隔离原则(Interface Segregation Principle,)使用多个专门的接口比使用单一的总接口要好。一个类对另外一个类的依赖性应当是建立在最小的接口上的。
大白话:这个原则和单一职责原则有点象,好吧,其实很难区分的。主要区分点在“职责”和“隔离”两个词上。职责要求按类的功能单一性定义接口、类、方法。隔离要求最细化的定义接口,尽量避免大而全的接口,不然接口一改,所以的实现类一片报红,请回答我,有没有?
实践:情愿让类实现多个单一接口,也不要实现一个大而全的接口。
2.5、迪米特原则
官方解释:迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。
大白话:电视剧经常有这个台词:“你知道的太多了”,然后一枪被崩了。这个原则也是这个道理,就是一个类尽量减少和其他类组合,这样能减少类之间的耦合,如果实现要关联,可以通过第三者(朋友)。门面模式和中介者模式就是这个原则的体现。
实践:一个类里,尽量减少与太多的类接触,如果不可避免,可以用一个中介类代替。
2.6、开闭原则
官方解释:在面向对象编程领域中,开闭原则规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的”,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。该特性在产品化的环境中是特别有价值的,在这种环境中,改变源代码需要代码审查,单元测试以及诸如此类的用以确保产品使用质量的过程。遵循这种原则的代码在扩展时并不发生改变,因此无需上述的过程。
大白话:其实这个原则实操性不强。如果这个原则是思想的话,上面的五个原则就是这个思想的实践,如果我们代码真的能做到职责单一、面向抽象编程且清爽的编码规范,则会很容易实现开闭原则。但现实情况是,我们的代码模块与模块,类与类高度耦合、很难见到接口或抽象类、甚至全是面向过程编码。所以在实际业务中,我们改一点代码,都要涉及很多项目、类、方法。改完很自信地说:我改动不大,不会影响线上业务的,不用测试,直接上线。请回答我,有没有?
实践:尽量避免修改原有的代码(一点不改也不太可能),前期编码的时候留好扩展点,使用继承增加新子类的方式修改原有业务。
三、总结
在学习设计原则和模式的时候,请先保证自己写出的代码是整洁且符合规范的,不然再好的原则和模式也拯救不了你那一大坨一大坨的类和方法,最起码的读你写的某一个方法的时候,不用拖滚动条。
真正的理解你所在公司的业务和需求,先在产品和文档级别上简化业务、理清编码的思路,这样更准确地找出业务的变化点和扩展点。
设计模式其实就是封装变化,所以编码的时候多用心变化点,有变化了就考虑抽象出接口或抽象类。当然,在实际业务中,多是增删改查,如果一昧的到处定义接口,也会增加复杂度,这些可以自己权衡或按团队的编码规范实施。
不要刻意地去背这些模式或者照着敲几遍,没用的!实际业务编码中,发现业务变化或扩展点后再去找适合的模式。