浅谈全局视角下的设计模式
写在前面:
以下内容,更多的是自己的思考总结,不可避免出现有争议的地方,请谨慎食用。
浅谈全局视角下的设计模式
- 1、业务开发经常使用的设计模式有哪些?
- 2、为什么有些设计模式不常见呢?
- 3、为什么这些设计模式常见?
- 4、设计模式代码结构很像,如何区分?
- 5、不用拘泥于设计模式的代码形式
在设计模式(GOF23)的学习过程中,我们时常会忽略设计模式的一个大背景,那就是设计模式的分类,然后径直走向每种设计模式怎么写、怎么用。我觉得还是有必要深刻理解下设计模式的分类,它们分别是创建型、结构型、行为型:
- 创建型设计模式主要解决“对象的创建”问题;
- 结构型设计模式主要解决“类或对象的组合”问题;
- 行为型设计模式主要解决的就是“类或对象之间的交互”问题。
基于这三个大的分类,标识着设计模式是在前人解决面相过程中对象和类的三种问题的过程中,抽象并总结出相关的设计方法论,最终称其为GOF23。
1、业务开发经常使用的设计模式有哪些?
在我平时的业务开发中,让我感触最深的就是,单例+工厂+模板+策略+适配这一套组合拳,我只能说三个模式打底。其余的场景一些模式如建造者、桥接、装饰者、责任链、观察者、状态,尽管它们适合的业务场景也很多,但是由于彼此之间业务场景相差比较大,所以更多的单独出现。不过值得一提的是,我认为模板方法这个模式它是业务代码中的万金油,稍微抽象一下业务场景都能用上。
注意,像代理模式这种,我认为在业务代码中其实并不常见,我是不太赞成“框架源码帮我们实现了”那就是高频使用这种说法,毕竟我们天天在用String变量,我们也没有说享元模式是常用的设计模式,所以我将代理模式归到了不常见中(迭代器模式同样如此)。
2、为什么有些设计模式不常见呢?
在今天的我看来,理由主要有三
- 不常见的设计模式更像是常见设计模式的一种补充。比如访问者模式和策略模式,同样是为了解决业务场景中业务对象和其对应的业务动作之间的变化。如果业务对象在动作上一定,但是数量上不一定,那么自然是策略模式,但如果业务对象数量上一定,但是业务动作上变化频繁,自然为了满足相关的设计原则,自然在设计上就优先考虑访问者模式。同时,现实业务场景中往往是前者,就算是出现了后者,我们基本都是秉承着屎上雕花的原则,而非重新设计;
- 运用场景单一,且高度依赖具体某种业务对象。如组合模式就是用来处理树结构;备忘录模式就是用来处理对象的备份和恢复;解释器模式就是用来处理语言的语法规则;
- 随着软件设计的不断发展,相关的设计模式出现了可替代方案。如原型模式我们可以使用BeanUtils.copy进行复制;门面模式和中介模式完全就是缩小版的DDD领域驱动;享元模式要么就是集合类中间相互传递,要么就干脆创建一个新的,毕竟我们不差那点性能;命令模式则可以使用Java8中的函数式编程;业务代码中代理模式基本不会写,更多的都是框架帮我们实现(如AOP),我们更多学习的是动态代理的思想;迭代器模式在业务开发中直接使用现有集合就好,如果集合满足不了,那就只能说名用的不对。
3、为什么这些设计模式常见?
理由同样有三
- 现实业务高度流程化。以传统金融业务为例,我们更多是对既定事实的描述,换句话说就是东西本身就存在,我们要做的只是一个翻译的动作。今天翻译票据业务,明天翻译现券业务,后天翻译存单业务,在这些场景下,单例、模板、策略、工厂天然就适配流程化的东西。在这个过程中,但凡出现一个涉及接口的兼容性问题,适配器模式的出现只能说是时间问题;
- 尽管业务场景单一,但这些设计模式涉及的对象太多。如状态模式,商品交易订单有状态,报销审批单也有状态,夸张的说,任务名词都存在生命周期,存在生命周期就会有状态;如责任链模式,事物由一个状态变为另一个状态都会有一个过程,这个过程只要出现了可以横向进行抽象的流程,那责任链就可以派上用场;任何名词都可以有多个维度的形容词,如描述手机可以从品牌、颜色、大小等多个维度,但凡业务需求涉及到了维度层面,桥接模式也就天然匹配了,在这个描述的过程,使用装饰者模式就再正常不过了;如构建者模式,事物从0到1,总是少不了构建的过程,想要有设计的构建,构建者模式总能施展拳脚。前面都是在说名词,现在来说说动词,都说唯一不变的是变化本身,事物发生变化,那么总是一发而动全身,毕竟任何事物都不会是独立的存在,想要影响的足够有规则、足够优雅、足够的称得上优秀的软件设计,观察者模式给出了解决方案;
- 二八定理。20%的设计模式囊括了业务开发中的80%业务场景,由于大部分业务场景都是这些设计模式,直接学习这些常见的设计模式,在时间和精力上效果效益是最高的。与此同时,随着我们业务开发代码量的增加,就算出现了符合某些不常见的设计模式的业务场景,我们的做法往往变成规避它(甚至都不知道当前场景符合),而那些常见的由于我们很熟悉,以至于它们越来越常见。最终形成了所谓的常见的设计模式。
4、设计模式代码结构很像,如何区分?
就像我开头说的,设计模式的出现是为了解决问题,而不是为了设计而设计。所以接触到一个新的设计模式,我们应该明确这个设计模式是为了干什么,即不能光看代码实现,而是要看设计意图,我们需要根据业务场景选择设计模式,而不是拿设计模式去套业务场景。
明白了这个前提,当我们面对一个想完成解决耦合的业务场景,就需要明确,解决谁的耦合,解决的耦合是什么,解决耦合是为了干什么。
- 如果要解决的耦合是为了让业务对象变化后,后续动作之间不再相互影响,那么我们可以想到责任链和观察者,如果动作差异过大,我们则优先考虑观察者模式,如果想要涉及上下文状态,不妨考虑下责任链;
- 如果要解决的耦合是为了让对象能够适应更有规则变化,那我们可以使用桥接模式、装饰器模式,那如果业务对象是由多个维度组合,那我们可以考虑桥接模式。如果业务对象是单一维度,但是其本身有很多条件,那么就可以考虑装饰器模式。甚至于如果想要补充一些非业务对象功能本身的功能,我们还可以想到代理模式;
- 如果要解决的耦合是为了让同属于一批流程下的业务流程能自己变化自己的,那么模板方法模式、策略模式也就应运而生。
5、不用拘泥于设计模式的代码形式
曾经在有简单总结过责任链设计模式在部分框架源码中的使用,浅谈责任链设计模式在框架源码中的运用。我们不难发现,同样是控制执行顺序有直接用list集合遍历的,还有用递归调用的,但是无论怎么变,我们都叫他责任链,因为我们发现它解决问题的背景,就是在解决一个流程式的问题,细节如何已经并不是那么重要。同样的,如果我们今天想使用动态代理功能,我们可以去实现InvocationHandler接口,还可以直接使用AOP切面。到了观察者模式上,我们可以使用JDK自带的Observer接口,还可以使用Spring的EventListener注解。一切的一切都是以设计模式的思想为指导。
写在最后
我在前期学习设计模式的过程中,总都会陷入代码的设计细节,总想理性的区别出不同设计模式在方法、类和接口上的继承、组合和实现等关联关系,最终发现很多设计模式在代码风格上极为相似。当然,今天的我也并非否定曾经的自己,我认为学习一个东西,从0到1总会经历一个过程,在不断深入研究学习的基础上,不同阶段对同一个事物总会有新的认识,没有曾经那些不妥当的认识和理解,学习又怎会让我们感受到成长的快乐?