浅谈我对设计模式基本原则的理解

“设计模式”这个东西,其实在大学的时候老师就有提到过,只不过当时段位不够,不能理解。
工作后又有一时没一时的看了一些设计模式,但是都没有内化,只是知道、认识。
直到前一段时间在工作中突然灵光乍现,主动使用了一些设计模式,才慢慢体会到“设计模式”的美妙。

再说到设计模式的基本原则,网上有六大原则也有七大原则,还有一些其他的原则,这里仅仅说我体会到的几项原则吧。

1. 单一职责原则

这个应该是最好理解的,简单的说就是一个类只负责一项职责,如果一个任务涉及到多个职责,那么应该有多个类去处理,而不是把所有的职责都写到一个类中。

这个问题最常见于应届生,一个功能的代码全都在一个类当中,极端的可能一个大功能的代码都在一个方法中写完了,大概是因为在学校做的任务都非常简单,养成了这个不良习惯吧。
而在工作中情况也不理想,我看过很多同事写的代码,也在github、csdn上看过很多别人的代码,经常也会出现一个service担负了多项职责的问题。

举一个例子,我们负责订单相关API接口的实现,一般按照MVC模式的情况下,我们会有OrderController,OrderService,OrderDao这样一系列的class和interface。如果订单相关的API不多也不复杂,那么还好。但是,如果订单相关的API又多又复杂,那么OrderService可能就会非常大。
怎么办呢?如果我们能在开始就考虑到这种情况,就能避开。比如我们可以采用策略模式,将不同的接口分流到不同的实现类中,就不会出现一个臃肿的OrderService。此外我们还能获得额外的优点:在添加、修改、删除某个接口时,不会对其他接口产生影响。

如果我们就是不遵守单一职责原则又会怎么样呢?没事的,反正是给自己挖坑。
说两个我前不久才遇到的例子:
1.由于需求变动,某个同事负责修改某个接口,但是由于接口之间的代码过度耦合,在修改接口时影响到了其他接口,而测试仅仅测了修改的接口,没有对其他接口进行回测,结果上线后出现问题。
2.还是1中的项目,由于确实是几年前的老项目了,某次开会时经理提到重构该项目,但是该项目中某一个关键接口承担的多个职责,而且需求到底是怎么样的也没人能说清楚,刚好不久后又有新任务来,所以这一坨屎仍然被放在那里。
试想,如果当年遵循了单一职责原则,那么在1中,至少不会影响其他接口。而在重构时,由于职责单一,我们很容易知道这段代码做了什么。

2.开闭原则

“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的。”
这句话也好理解,就拿上面我们提到的策略模式来说,当你添加新的策略时,不会修改到别的策略文件,甚至都不用修改策略调用的context的代码。
有什么好处呢?首先,没有改动就没有风险,不用担心其他地方出bug,出bug也是他本身就存在的bug。其次,如果你们有代码审查的话,那么可以减少需要审查的代码,当然国内好像没几家公司这么严格。

但是姓杠的同学就会说,如果需要修改一个策略,那么必然就要改动原来的代码,这个“闭”不了!
话不能说绝对了,有的情况下确实直接修改这个策略文件最方便,但是我们也有别的方法来解决这个问题。
比如,我们要在原来的基础上,再添加其他一些功能,那么我们可以采用装饰器模式去实现。
如果要修改某个函数的功能,在不能修改原来类文件的要求下,我们也可以继承改类,重写某个方法,然后用子类去代替父类。
当然了,大多数情况下上面的两个方案都不是好的选择。因为大多数情况下我们都是修改需求,运气不好还是大改,所以我们可以新创建一个类去重新实现需求。不过这个时候就需要改动到调用的代码了,为了避免修改调用处的代码,我们可以使用interface,在调用处调用接口的方法,在实现类中实现对应的方法。

同样在SpringBoot中,我们常说“约定大于配置”,其实就是按照约定SpringBoot已经帮我们配置好了。但是如果我们真的需要自己配置,难道需要修改SpringBoot的jar包吗?当然不可能。我们可以通过@Configuration注解创建一个配置类,替换SpringBoot默认的自动配置类。所以我们不用修改SpringBoot的代码,就完成了扩展功能,这就是开闭原则。

3.里氏替换原则

父类出现的地方,一定可以用子类去代替。
怎么理解这句话?B类继承了A类,在new A()的地方,我们一定可以使用new B()去代替。首先B类肯定是包含了所有A类的方法。其次,B类没有修改A类的方法。

在开闭原则中我们提到:“如果要修改某个函数的功能,在不能修改原来类文件的要求下,我们也可以继承改类,重写某个方法,然后用子类去代替父类。”看上去我们用子类去代替了父类,但这其实违背了里氏替换原则,因为子类重写了父类的方法,如果调用子类的该方法,就会得到一个和预期不符的值,从而导致出错。

所以,满足里氏替换原则的条件是子类不能修改父类的方法,只能对其进行扩展。

4.依赖倒置原则

程序要依赖于抽象接口,不要依赖于具体实现。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

回想一下在开闭原则中,我们还提到使用interface避免了对调用方代码的修改,其实就是依赖倒置的一个体现。

5.迪米特法则(最少知道原则)

一个对象应当对其他对象有尽可能少的了解。
这句话应该是最不好理解的了。具体的说,就是在一个类中我们尽肯能的少调用其他类的方法。再说具体点,就是在一个类中,尽可能少的@Autowired。

这个原则的初衷在于降低个各类之间的耦合。想想,遵循迪米特法则是A–B–C–D这样的一条线,而不遵守迪米特法则,可能就是A–D,A–B–D,A–C–D…..就形成了一个网,而形成网后想要修改其中的某一个对象,那么对整个系统的影响就会比较大。

其实设计模式的原则挺多,在实际编程时,总会遇到违背某一个法则的情况,这种也不用太纠结,做出最优选择即可。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据