设计模式
设计模式原则,常见的设计模式、领域驱动模型
[TOC]
只记录常用设计模式
设计模式六大原则
单一职责原则(SRP):一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因。
开闭原则(OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
里氏代换原则(LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
依赖倒转原则(DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。如 控制反转和依赖注入
接口隔离原则(ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
迪米特法则(LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。
常见设计模式
创建型模式是将创建和使用代码解耦
结构型模式是将不同功能代码解耦
行为型模式是将不同的行为代码解耦
创建型
单例模式
主要解决:一个全局使用的类被频繁创建和销毁,数据在应用上只保持一份,解决资源访问冲突的问题,就可以使用单例模式
简单工厂模式
创建型模式
主要解决接口选择问题
定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行,还有另一种工厂需要用到单例,通过在静态代码块里先初始化好对象,然后+key放到map里
util.Calendar、util.ResourceBundle、text.NumberFormat、nio.charset.Charset、util.EnumSet、DI容器
应用场景:当有一段需要通过if-else的代码来判断初始化哪些对象的时候,就可考虑
优点:调用者只需要知道名字就能创建对象,不关心对象的具体实现;
缺点:数量太多会导致 if-else 膨胀,每次有新的对象要加入工厂需要需改工厂方法,不符合开闭原则

工厂模式
优点:调用方不需要负责对象的创建,明确了各个类的职责;如果有新对象增加,只需要增加一个具体的类和具体的工厂即可;解决简单工厂有大量 if-else 问题;
缺点:每次有新对象都要写一套 类 和 工厂类,每个类都要有对应的工厂类,代码量很大;
感觉跟简单工厂模式的差别就是工厂模式是先创建对象工厂,再创建对象,简单工厂模式是直接通过工厂创建对象,前者需要知道对象工厂的名字,后者需要对象名字;
抽象工厂模式
与工厂模式类似,也创建型模式
工厂方法是一个工厂,根据传入参数生产不同实例,而抽象工厂则加多一层工厂获取。抽象工厂属于大工厂,根据传入参数产生工厂实例,在通过这个工厂,传入参数获取对象实例
跟工厂模式的差别,抽象工厂模式先通过抽象工厂生产工厂,再通过工厂生产对象
建造者模式
其实就是链式调用,主要为了解决构造方法参数过多,且需要校验的情况下,如果参数过多且需要校验,使用构造方法或者set方法来实例化对象不太方便,同时,使用建造者模式还可以把对象处理成初始化后属性不可变得对象
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象
原型模式
其实就是拷贝,把对于那些创建成本较大(比如创建要进行复杂计算、IO读取等)且同一类但不同对象得对象,利用原有对象进行拷贝,来达到创建新对象的目的
常见方式有序列化再反序列化,或者递归遍历对象里的字段进行创建和赋值,深拷贝或者浅拷贝,这里要注意浅拷贝和深拷贝问题
结构型
代理模式
结构型模式
通过代理类扩展被代理类的能力,代理类和被代理类实现同一接口,重写其中的方法,在代理类中传入被代理类的实例,在两者相同的方法中,调用被代理类的该方法,同时可以处理其他逻辑,达到扩展的能力
1、和适配器模式的区别:适配器模式主要是两个代表不同维度的接口,它们的实现通过组合的方式扩展原来的功能,而代理模式是代理类和被代理类实现相同的接口,通过代理类调用被代理类相同的方法来达到对代理类补充的作用。
2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
下面的例子属于静态代理,其他代理请看动态代理和CGLIB代理

装饰器模式
结构型模式
装饰类向被装饰类添加新功能,同时又不改变其结构,作为现被装饰类的包装,继承的一种代替,主要解决多层次继承的问题
装饰类和被装饰类可以独立发展,不会相互耦合
跟代理模式很像,本质上代码差别不大,只是意图不太一样。
区别:装饰器可以一层一层装饰,每次装饰可以增强或扩展被装饰者的功能,功能是相关的,是对功能的增强。外部是知道具体的被装饰者的(对装饰器传入被装饰对象),然后不断通过装饰达到对原有功能的增强。
而代理模式是一层,代理类控制被代理类,控制被代理对象的访问,外部并不关心被代理的对象是谁(被代理类是在代理类内部进行构造,不从外部传入),只知道通过代理对象可以实现对被代理对象的功能补充,是对代理对象的功能补充,直接加强。
最典型的例子就是IO类了,每个io类都继承了in/outputStream(带了默认实现),同时又持有in/outputStream,调用的本质还是在调持有的in/outputStream方法的基础上进行增强
题外话,为啥BufferedInputStream不直接继承InputStream,而是继承FileInputStream?
原因是如果BufferInputStream继承并同时持有inputStream,由于BufferedInputStream只对部分方法增加buffer功能,对那些不需要增强的方法的调用就需要显式调用持有的inputStream,而如果先通过FileInputStream继承并组合InputStream,并调用持有的inputStream的默认实现,就不用写这部分多余的代码了

接口Shape表示形状,它的实现类是圆Circle类,统一的抽象装饰类ShapeDecorator,带有Shape类的引用,其 实现的装饰类RedShapeDecorator通过传入具体的形状实例,来对共同的方法做增强
适配器模式
结构型模式
接口适配器使得实现了不同接口的类可以通过适配器的选择而工作,主要是规避接口不兼容的问题,本质是使用一组类和接口充当适配器,包在被适配的类和接口上,具体又是实现或组合
典型例子:Arrays#asList(),Collections#list(),Collections#enumeration()

有两个接口AdvancedMediaPlayer和MediaPlayer,它们都有不同的作用,但它们的作用又很相似,AdvancedMediaPlayer可以播放vlc格式或者mp4格式,而MediaPlayer只能单纯的播放,多接口适配
如果想要让MediaPlayer能播放不同格式的音乐,就需要适配了,适配器实现MediaPlayer接口,根据传入的参数来判断需要实例化哪种播放器,并在播放方法里执行相应播放器的播放方法
其他例子,以下例子来自极客时间-设计模式之美,都是单接口适配
1、封装外部sdk接口
2、统一多个类的接口
3、替换依赖的外部系统
桥接模式
结构型模式
抽象与实现分离,桥接接口类和抽象类,实现解耦,这里其实并不一定是要抽象类,抽象类只是代表一组现实的抽象,即一个类存在两个(或多个)独立变化的维度,通过组合的方式,让这两个(或多个)维度可以独立进行扩展
典型例子:JDBC,仅修改Class.forName("com.mysql.jdbc.Driver"),即可把驱动换成别的数据库
JDBC的做法是提供Driver接口,数据库厂商实现该接口提供不同的数据库能力,并调用DriverManager进行注册,后续通过DriverManager获取connection并进行CRUD操作,都由DriverManager委派给具体的Driver做

桥接接口类和抽象类,接口类实现上色,抽象类实现形状,在抽象类中引入接口,通过不同的组合实现不同的功能
代理模式、桥接模式、装饰器模式、适配器模式的区别
**代理模式:**代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
**桥接模式:**桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
**装饰器模式:**装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
**适配器模式:**适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。
其实这几种设计模式在代码层面上是很相似的,本质只是设计意图的不同,应对的场景不同
门面(外观)模式
结构型模式
本质是对接口的组合,比如有子系统或者子模块提供了b、c、d接口,都是些职责比较单一的接口,可以在上层提供一个大而全的接口,使用到了b、c、d接口提供的功能,简化了调用者的调用关系处理
组合模式
结构型模式
本质是多叉树,根节点和子节点继承或实现同一个接口,以方便递归处理树形结构的数据
主要处理树形结构数据
享元模式
结构型模式
享元,即共享单元,一般是通过复用不可变对象达到节省内存的作用,通过map + 工厂模式来达到复用的目的,但是对于GC并不友好,如果该对象并不常用,也可以使用弱引用或软引用相关的hashMap来存储,方便垃圾回收
典型例子:java中的Integer类,对于多个-128
127的包装类型对象,底层的内存地址是同一个,Integer类里有个IntegerCache内部类,相当于享元对象的工厂,缓存着-128127之间的数据,类似的,如 Long、 Short、 Byte包装类型也用到了这种方法;还有String类的字符串常量池,会缓存string字面量,String类也提供了intern方法方便我们将字符串存入常量池
行为型
责任链模式
行为型模式
每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推,即:将所有接受者连成一条链,请求沿着这条链传递,直到有对象处理
典型例子:servlet filter、spring interceptor、logger

这是模拟日志级别打印的例子:日志抽象类规定每个结点的日志等级和需要重写的方法,参数传递处理的方法,可以看成一个链表上结点的抽象;具体的类实现该抽象类重写共同方法当成每一个结点,最后将这些结点连成链即可
迭代器模式
行为型模式
用于顺序访问集合对象的元素,不需要知道集合对象的底层表示,不会暴露该对象的内部表示,迭代器内部使用游标记录当前位置信息,每个迭代器独享游标信息,这样当我们创建不同的迭代器对不同的容器进行遍历的时候就不会互相影响
针对复杂的数据结构,比如树、图的遍历,使用迭代器模式会更加有效
对于迭代过程中通过对容器增加或删除元素会有问题, java使用fail-fast机制,即遍历每个元素的时候会比较modCount的值和调用迭代器时的expectedModCount的值比较来实现fail-fast,达到报错的目的。此外,如果想要删除,需要通过迭代器来删除才可以,java中使用lastRet变量来记录上一个游标,以保证在删除当前元素后,游标能正确指向。

访问者模式
行为型
允许一个或者多个操作应用到一组对象上,解耦操作和对象本身,比如对于多种文件类型,可以使用多种不同的执行器作用在不同的文件上
备忘录模式(快照)
行为型
一般栈来保存副本,入栈的元素都会叠加上一个元素的内容,以此来实现顺序撤销和恢复功能
主要是用来防丢失、撤销、恢复
状态模式
行为型
状态 -> 事件 -> 动作 -> 状态改变 或者 状态 -> 事件 -> 状态改变 -> 动作
分支逻辑法 或者 查表法 或 通过将分支判断抽成类来实现上述 当状态接收到事件后进行业务逻辑动作后改变状态
观察者模式
行为型
在一对多关系中,当一个对象被修改时,则会自动通知它的依赖对象
典型例子:消息队列的发布/订阅模型
硬要说的话,观察者模式和发布订阅模式还是有一定差别的
观察者是当被观察者有状态发生改变时,通知观察者;而发布订阅是发布者把消息丢到消息队列,消息队列根据消息发给对应的订阅者,区别就是有没有第三方存在,消息的双方知不知道彼此的存在
发布订阅:

模板方法
行为型
一个抽象类抽取一些通用方法合并成新方法并用final修饰作为模板,它的子类继承此抽象类,通过重写通用方法,来实现不一样的模板方法
典型例子:JUC包里的AQS和其子类、util.Collections#sort()、InputStream#skip()、InputStream#read()、servlet、junit
模板方法与回调的区别:回调的作用与模板方法类似,都是通过自由替换某个方法来实现不同的功能,但回调基于组合关系,而模板方法基于继承关系,同步回调类似模板方法,异步回调类似观察者模式

策略模式
行为型
通过定义一个通用策略接口 + 一组策略的实现,由调用者在运行时动态选择某一策略完成业务逻辑,常用搭配是 策略 + 工厂模式,根据不同类型选择不同策略
常用的场景是使用策略模式来避免膨胀的分支判断,缺点是策略类会变多,另外,使用查表法也可避免膨胀的分支判断
主要解决:将不同算法封装成策略,根据条件选择合适的策略
命令模式
行为型
将函数方法封装成对象,类似c语言的函数指针,数据驱动,将事件和数据封装成对象(命令),最后执行,有点像策略模式,但是命令模式更侧重将行为请求者和实现者解耦
解释器模式
行为型
针对某种语言制定对应的语法,在代码中的体现是,对某种模式的解析抽成方法,降低代码复杂度
典型例子:后缀表达式的解析、sql解析、正则表达式解析
中介模式
行为型
引入中间层,将原来多对多的关系转换为一对多的关系,解耦对象间的关系,类似观察者模式,区别在于观察者模式中,数据的流向、对象间的关系是单向的,观察者就是观察者、被观察者就是被观察者,不会轻易改变,而中介模式是由一个中间对象来处理各个对象间的关系,类似数据总线
典型例子:聊天室
对于EventBus,个人认为它不是中介模式,硬要说话,处理事件的中心类,也算是中介,但是主要是发送事件的对象和处理事件的对象并没有很直接的关系,事件处理者仅关心接收到的是什么事件,并不关心事件是由谁发出的
领域驱动模型 DDD
贫血模型,面向过程,业务逻辑和实体分开,实体仅声明需要的属性,业务逻辑的处理发生在service类,实体只用来存数据,比如各种vo、bo、do
领域驱动模型是一种面向对象的模型、充血模型,更多是用在微服务、业务复杂的场景
具体表现在 业务逻辑的处理是发生在实体类里,而不是发生在service类里,service类只是对各种vo、bo、do转换成需要的领域对象,对它们进行调度,执行它们的方法来完成业务,service甚至可以不感知领域对象的属性变化,与controller和dao层打交道,解耦流程性代码和业务代码,负责非功能性或者与第三方系统交互的工作,比如分布式事务、邮件、消息、rpc调用等,使得领域对象方法和属性复用性提高,业务更加内聚
Last updated