设计模式
我在学习设计模式也经历了好几个阶段,从刚开始的看不懂,到慢慢觉得这有什么好处吗,再到后来觉得有点用,目前是在工作中,才体会到设计模式的重大价值,可以说设计模式我也学过很多遍了,希望这次在梳理一遍。主打的一个目的就1个知道在什么场景下使用什么设计模型,希望有理解不够深入的地方,可以一起讨论。 只考虑Java语言,只考虑业务开发和中间件研发,能用业务开发说明就用业务开发说明。
# 创建型模型
在创建对象的时候,尤其是创建复杂对象,我们可以考虑用此类设计模型。
# 工厂方法(Factory)---高频
场景1
如果我们的代码有好几种不同流程,比如说通过if-else,好几个相同的输入,相同的输出。那么这个时候我们可以使用工厂方法进行优化。 一般情况下,写一个工厂类,接受若干字符串,枚举参数,这个可以是前端传递的,也可以我们写死的。然后需要把不同的流程实现同一个接口。 即使目前只有一个,只要未来可能扩充的,都可以用工厂实现,这样新增的时候,改动就非常小,不担心改坏。
场景2
在中间件中,为了方便我们增加实现,也会提供工厂接口,让我们实现子类便可以替换。最明显的是spring的,我们可以定义实现bean,来替换原来本的bean。
场景3
spring ioc如果有对象创建比较复杂,我们可以在spring的工厂下创建这个bean,这样就可以直接注入这个对象,省去复杂的配置,或者说最关键的是,把对象的构建和使用分离到2个不同的类。
场景4
单例工程,如果我们有多个对象可以复用,那么我们可以通过工厂方法来获取这些对象,本质就是单例模式。
提示
一句话:有多种实现流程,放在map里面,需要什么流程取什么流程,但是需要统一接口。
# 抽象工厂(Abstract Factory)
区别与工厂,我们通过他只需要获取一个对象,但是如果我们需要通过工程获取一系列对象,而且这些对象必须直接互相搭配,或者理解说只有一个工厂管理。 这个本质就是有好几个工厂方法的接口,然后有不同的工厂实现类,作用和用途,以我的理解,和上面的工厂方法没有任何区别。
提示
一句话:需要获取一致处理对象的工厂。
# 建造者模式(Builder)---常用
最直观的就是Mybatis Plus用来构建SQL的builder
场景1
如果我们要构建的对象非常复杂,直观来讲,就是我们写一个创建对象的代码,写了很多行,而且会被用在各种地方,而且各种地方都不一样,里面有很复杂的逻辑。
场景2
如果我们一个类有很多构建方法,有一些是可选的,如果用Java去实现,那么会写很多方法,非常的麻烦,但是builder模式可以让我们按需构建,而且支持校验。
场景3
构建复杂嵌套对象的时候,可以使用Java的lambda表达式,这样可以让人很轻松的看懂对象是怎么嵌套的。
提示
一句话:在构建对象复杂的情况下,仅仅保留构建对象需要的参数,通过链式调用的方法传递。
# 原型模式(Clone,Prototype)
说实话,我觉得这个就是Java里面clone接口的实现,不算什么设计模型。目前的理解还考虑不到使用的场景。
提示
一句话:克隆对象。
# 单例模式(Singleton)---常用
没有必要使用复杂的机制,往往最简单的写法就可以,一些在启动时需要获取系统数据的,一定不能是懒汉式,一定启动的时候就报错。
场景1
全局只使用这一个对象,关键点有2个,一个是把构建方法私有化,另一个就是一定要考虑并发的问题,使用了单例必然和并非有纠缠。
提示
一句话:全局共享一个对象,要特别注意并发安全
# 结构型模式
这个说的是组长对象和类,但是我确实分不清和行为有什么区别,但是没什么必要,我们直接看具体的模式。
# 适配器模式(Adapter)---常用
这个强调是我们把对象进行一些转换,以此来调用别人的接口。
场景1
在我们调用别人接口,如果对方的对象和我们对象有相同的含义,但是表示不同,我们可以使用适配器,到时候我们直接调用适配器。
提示
一句话:包装对象的转化流程,适配其他类的接口。
# 桥接模式(Bridge)---优先组合而不是继承
本质是不是一种设计模式,感觉优先组合而不是继承完全的包含了这种模式。
场景1
我们有2类对象,需要他们支持两两组合。如果不用设计模式,类的数量会非常多。 但是我们通过组合来实现这个需求,让这2类对象有抽象的实现就可以。
提示
一句话:优先组合而不是继承
# 组合模式(Composite)
感觉比较低频。 背景是树状对象结构,比如多级菜单,核心关键是容器和内容节点都使用的同一接口,接口方法肯定是对简单节点和容器都有意义,比如收藏。容器可以包含简单节点和其他容器。
提示
一句话:对容器和节点采用一致的接口处理。
# 装饰器模式(Wrapper,Decorator)---常用
最典型的就是Java的文件读写类直接使用了这种模型。 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。 只需要把一个类传入另一个类中,作为一个变量,那么原来的类的行为就被扩展了。
场景1
你希望运行时可以扩展对象的行为,比如多级缓存。
场景2
一个类被final修饰了,但是你仍然希望继承他,可以使用这种模式。
提示
一句话:可以增强原来对象的功能,扩展了接口的能力。
# 外观模式(Facade)---常用
这个本质就是我们所说的包一层,或者什么什么utils
场景1
你需要调用多个对象来配合实现某一件事情,可以把他们放在一个类里面,让我们直接调用。
场景2
你觉得一个类,框架,提供的接口不好用,你就自己包一层。
场景3
如果你不想让别人调用某一些方法,那么你就包一层,那些方法不给予实现。
提示
一句话:把一个或多个类,自己包了一层,提供更简单直观的接口。
# 享元模式(Flyweight Pattern)
这个模式就是有一个类专门管理这些对象的创建和缓存复用。让外部感知不到这个过程,以为是创建了一个新的对象。比如一些链接池。
场景1
在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
提示
一句话:对象池,复用对象,节省内存,但是用户感知不到。
# 代理模式(Proxy)--常用
使用相同的接口,但是功能被增强了,一般都是动态代理。 RPC远程代理,Cacha代理,懒加载代理等等。
场景1
spring aop的使用场景,日志,用户信息
提示
一句话:相同的接口,使得用户感知不出来的情况下,增强了原来的对象。
# 行为模式
行为模式负责对象间的高效沟通和职责委派。
# 责任链模式(Chain)--高频
场景1
处理某个数据有很长很长的流程,而且可能多个流程有一些相同的内容。那么我们就使用责任链,调用完一个调用下一个,一般有一个context来保存数据。 可以支持中断,比如在context里面写一个stop方法,那么后面的就不执行了。 主要是隔离和复用!
场景2
有多种处理方法,但是因为不确定是什么请求,那就遍历每一个,遇到一个可以处理的就进行处理,这种一般都有support方法。
提示
一句话:把流程上的多个处理隔离到类里面,可以解耦和复用
# 命令模式(Action)
场景1
这个应该是客户端用的比较多,我的理解就是有很多命令和有很多实现类,我们不想知道这个命令是哪些实现类来实现的,实现类也不关心是谁提出的命令,那么中间就需要有一个对象来实现这个转发。命令的本质就是一个对象。
提示
一句话:用对象来代替具体的命令,把调用放和实现方分离。
# 迭代器模式(Iterator)
场景1
不管你是什么数据结构,只需要通过一个迭代器去遍历就可以,不管是遍历数据,列表,集合,甚至是树和图,你给我一个迭代器,我来遍历,甚至删除。 这个在需要给别人提供对象遍历的时候才用的,一般很少,因为Java都给我们写好了。
提示
一句话:提供了数据结构的遍历,即使不了解数据的原理也可以遍历。
# 中介者模式(Controller)
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 我觉得这个就是IOC,而且想不到什么应用场景。
提示
一句话:让一个对象来负责对象的关联,把关联复杂度集中在一起,方便管理。
# 备忘录模式
允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。不好评价,对我来说是完全没有用的。感觉不是一种模式,更像是具体的功能要求。
提示
一句话:可以支持数据回滚,而且外部不感知。
# 观察者模式(Listener、Observer)---常用
就是注册监听者,有事件发生的时候,遍历监听者把时间告诉他们。
场景1
类似于MQ的松耦合设计,这个比较常用,但是在很少不使用MQ直接使用这个。一般情况下,可以监听spring的一些事件。 如果有这种需求,可以实现,作用和意义同MQ,异步,解耦。
提示
一句话:注册观察者,事件发生的时候,发送给每一个观察者
# 状态模式(State)
场景1
一个业务对象会经历多种状态的互相转化。 需要我们实现一个接口,里面有很多方法,代表变成这种状态,每加一种状态就可以多加一种方法。然后每种状态都实现这个接口。就可以反应出每种状态的互相变化。 如果我们多加一个一种状态,由于编译器的限制,就可以强制我们考虑他和其他所有状态的转化。
提示
一句话:利用编译器的限制,强制我们考虑多种状态的互相转换。
# 策略模式(Strategy)---常用
场景1
解决一个问题有多种方式,可以把多种方式设计成不同的类,然后传给一个管理类,管理类可以执行不同的行为。这个很类似于传递一个lambda表达式进去,这个意义和工厂模式也很像。 对比一下模板方法。 模板方法模式基于继承机制: 它允许你通过扩展子类中的部分内容来改变部分算法。 策略基于组合机制: 你可以通过对相应行为提供不同的策略来改变对象的部分行为。 模板方法在类层次上运作, 因此它是静态的。 策略在对象层次上运作, 因此允许在运行时切换行为。
提示
一句话:把多种方法封装成对象,在运行时传递不同的对象,采用不同的算法策略。
# 模板方法模式(Abstract)---高频
场景1
有多种实现方法,他们大体的流程都是一样的,但是实现的细节有不同,那么就把这些细节的方法交给子类,然后父类负责调用子类的实现。
提示
一句话:多种方式,大体流程相同,细节实现不同,细节实现交给子类编写。
# 访问者模式(Visitor)
场景1
需要遍历一个复杂的嵌套对象,然后对面不同的对象采用不同的行为去处理。访问者就是编写了这个遍历的过程,然后具体的实现还是调用抽象模板子类方法,他负责了这个分发的过程。 最典型的比如我们递归去遍历一个文件夹下面的所有文件,我们不用管他是怎么递归的,只需要写怎么处理这些问题。
提示
一句话:把复杂对象遍历和处理实现分离,让我们只关注实现,不关注遍历。
# 设计模式六大原则
其实设计模式在实现和思路上有很多重复的地方,本质来说,六大原则就是抽取设计模式的核心思想,可以做到更高水平的境界。
# 开闭原则
对扩展开放,对修改关闭 这是基本所有设计模式的核心了,对扩展开放,因为扩展是新加的类,大多数情况下,不会影响原来的系统。 对修改关闭,最怕直接修改以前的代码了,容易出问题。
# 里氏代换原则
任何基类可以出现的地方,子类一定可以出现。规范我们子类编写的,不过我目前领悟不到这个。
# 依赖倒转原则
优先使用接口,抽象类编程,而不是实现类编程。
# 接口隔离原则
接口的功能要强相关,如果一个接口的函数,有几个看起来和其他的接口没关系,那么就要拆分成多个接口。
# 迪米特法则(最少知道原则)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。 尽可能的少暴露public的方法,参数,至于字段就更不用说了,肯定不能暴露。
# 合成复用原则
优先使用组合而不是继承。