设计模式

一、6大设计原则:

1.1 单一职责原则:

单一职责原则 (Single Responsibility Principle) SRP的定义: 应该有且仅有一个原因引起类的变更

单一职责原则适用于接口、类,同样适用于方法,一个方法尽可能只做一件事情,但是该原则最难划分的就是职责。一般建议来看,接口的设计一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。

1.2 里氏替换原则:

(Liskov Substitution Principle) LSP

4层含义:

1. 子类必须完全实现父类的方法
2. 子类可以有自己的个性
3. 覆盖或者实现父类的方法时输入参数可以被**放大**
4. 覆写或者实现父类的方法时输出结果可以被**缩小**

1.3 依赖倒置原则:

(Dependence Inversion Principle) DIP

3层含义:

1. 高层模块不应该依赖底层模块,两者都应该依赖其抽象
2. 抽象不应该依赖细节
3. 细节应该依赖抽象

在java语言中,抽象就是指接口或者抽象类,两者时不能直接被实例化的。细节就是实现类,实现接口或者抽象类而产生的类就是细节。

依赖倒置原则在java中就是: 面向接口编程(OOD)

1. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的
2. 接口或者抽象类不依赖于实现类
3. 实现类依赖接口或抽象类

采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

1.4 接口隔离原则:

建立单一的接口,不要建立臃肿庞大的接口。换句话说,就是接口尽量细化,同时接口中的方法尽量少

接口隔离原则单一职责区别在于两者的角度是不同的,单一职责是从业务逻辑划分的角度去看待,要求类和接口的职责要单一。而接口隔离原则是要求接口的方法尽量少,尽量提供专用的接口,有几个模块就有几个接口。

1.5 迪米特法则:

(Law of Demeter,) LoD

只与直接的朋友通信。朋友类的定义为:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。

1.6 开闭原则:

二、23种设计模式:

2.1 *单例模式:

定义:某一个类只有一个实例,而且自行实例化并向整个系统提供该实例。

特点:

1. 单例类只能有一个实例
2. 单例类必须自己创建自己的实例类

应用:单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。spring中对bean的管理默认都是单例模式,需要注意多线程使用的问题,避免对非静态变量使用不当导致数据不一致的情况发生,比如java中的日期类。

注意:多线程使用的时候,注意实例变量是否会因为多线程而产生状态不一致问题。

实现: 饿汉式、懒汉式、枚举类

2.2 *工厂方法模式:

定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

优点:

1. 良好的封装性,代码结构清晰,降低模块间的耦合性。
2. 可扩展性好,在增加新的产品类时,只需要添加新的子类,不用改动工厂类。
3. 高层模块只依赖产品的抽象,符合迪米特法则、依赖倒置原则和里氏替换原则。

应用场景:需要灵活、可扩展框架的时候,可以考虑采用工厂方法模式。

扩展:

1. 升级为多工厂模式,,对不同的产品定义不同的创造者
2. 替换单例模式, 采用反射的方式创建对象
3. 延迟初始化,用map容器保存生产的对象

2.3 *抽象工厂模式:

定义: 为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。

工厂方法模式提供了针对一类产品在创建时的低耦合的解决方案,抽象工厂模式是工厂方法模式的升级版,在有多个业务分类时,是非常好的解决方式。

优点:

1. 封装性,高层模块并不关心每个产品类是如何创建出来的,这是由具体的某一个工厂类关心的,高层模块只需要知道工厂类是谁就可以创建出需要的对象。
2. 产品内部的约束为非公开状态。

缺点:

1. 可扩展性不好,如果要增加一个新的产品,涉及到的改动很大。

2.4 模板方法模式:

定义:定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。仅仅使用java的继承机制来实现。

抽象模板的方法分为两类:

  1. 基本方法:

    也叫基本操作,是由子类实现的方法,并且在模板方法中被调用。

  2. 模板方法:

    可以有一个或者多个,一般有一个固定的实现逻辑,完成对基本方法的调用。

注:为了防止恶意操作,一般模板方法都加上final关键字,不允许被覆写。

优点:

1. 封装不变部分,扩展可变部分。
    不变部分由父类实现,可变部分通过继承由子类来扩展
2. 提取公共部分代码,便于维护。
3. 行为由父类控制,子类实现。

缺点:

高层模块并不是和具体实现完全解耦合。在模板方法中,子类的执行结果会影响父类的结果。

2.5 建造者模式:

定义:也叫生成器模式,将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。

优点:

1. 封装性。客户端不必知道产品内部的组成细节。
2. 建造者独立,易扩展。

应用场景:相同的方法,不同的执行顺序,产生不同的事件结果时,可以考虑使用建造者模式。

注:

​ 建造者模式最主要的功能就是基本方法的调用顺序的安排,通俗的说就是零件的装配,顺序不同产生的对象也不同;而工厂方法的重点是创建。

2.6 *代理模式:

定义:为其他对象提供一种代理以控制对这个对象的访问。

代理模式也叫委托模式,在日常应用中,代理模式可以提供非常好的访问控制,它有三种角色定义:

  • 抽象主题角色:可以是抽象类或者接口,是一个普通的业务类型的定义
  • 具体主题角色:也叫被委托角色、被代理角色,是业务逻辑的具体执行者
  • 代理主题角色:也叫委托类、代理类,负责对具体角色的应用,把抽象主题角色定义的方法委托给具体主题角色实现。

优点:

1. 职责清晰
2. 高扩展性

扩展:

  • 普通代理:调用者需要知道代理的存在,通过代理去调用真实角色的具体实现

  • 强制代理:调用者直接调用真实角色,不用关心代理是否存在,代理的产生是由真实角色决定的

  • 动态代理:动态代理是在实现阶段不用关心代理是谁,而在运行阶段才指定代理哪一个对象。相对来说,自己写代理类的方式就是静态代理。

2.7 原型模式:

定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

实现: java中实现Cloneable接口来标识这个对象是可拷贝的,这个接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被拷贝。那怎么才能从”有可能被拷贝“转换为”可以被拷贝“呢?通过重写clone方法,这个方法是在Object类中。

优点:

1. 性能优良: 原型模式是在内存二进制流中拷贝,比直接new一个对象性能好很多
2. 逃避构造函数的约束,直接在内存中拷贝,不会执行构造函数。相应的也是一个缺点,就是减少了约束

应用:

1. 资源优化场景,类初始化需要消耗非常多的资源
2. 性能和安全场景,通过new一个对象会需要非常多的数据准备或者权限访问,则可以使用原型模式。

注意事项:

  • 构造函数不会被执行

  • 深拷贝和浅拷贝:

    Object提供的方法clone只能拷贝本对象,对象内的数组、引用对象都不拷贝,还是执行原对象的内部元素地址,这种拷贝就属于浅拷贝。

    如果要实现深拷贝的话,可以通过手动对私有的类变量进行独立的拷贝。

  • clone和final冲突,要使用clone方法,类的成员变量上不要增加final关键字。

2.8 中介者模式:

定义:用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

优点:

1.减少了类间依赖,原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者。

缺点:

1. 中介者会变的很臃肿,而且逻辑复杂。

2.9 命令模式:

定义:命令模式是一个高内聚模式,将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

优点:

1. 类间解耦:调用者与接收者之间没有任何依赖关系
2. 可扩展性:Command子类非常容易扩展,而高层模块Invoker和Client不会产生严重的代码耦合
3.与其他模式组合,更优秀

缺点:

1. 关于Command子类扩展的问题,如果有很多个命令,那么Command子类会膨胀的非常大。(结合模板方法,可以减少Command子类膨胀的问题)

2.10 责任链模式:

定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

优点:

1. 解耦: 请求和处理分开,请求者可以不用知道是谁处理的,处理者可以不用直到请求的全貌

缺点:

1. 性能问题:每个请求都是从头到尾遍历链表
2. 调试不方便: 链条较长时,类似递归,逻辑比较复杂

2.11 装饰模式:

定义:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子更为灵活。

优点:

1. 装饰类和被装饰类独立,不会相互耦合
2. 装饰模式是继承关系的一个替代方案
3. 可以动态扩展一个实现类的功能

缺点:

1. 多层装饰比较复杂,定位问题麻烦

应用:

1. 需要扩展一个类的功能
2. 动态地给一个对象增加功能

2.12 策略模式:

定义:定义一组算法,将每个算法封装起来,并且使它们之间可以互换。

策略模式使用地是面向对象地继承和多态机制。

策略模式的重点就是封装角色,借用了代理模式的思路,它和代理模式的差别就是策略模式的封装角色和被封装的策略类不用时同一个接口,如果是同一个接口就成为了代理模式。

优点:

1. 策略自定义,自由切换
2. 避免使用多重条件判断
3. 扩展性良好

缺点:

1. 每一个策略都是一个策略类,复用可能性小,所以类数量会增多
2. 所有策略类都需要对外暴露

应用场景:

1. 多个类只有再算法或行为上稍有不同的场景
2. 算法需要自由切换的场景
3. 需要屏蔽算法规则

注:

如果系统中一个策略家族的具体策略数量超过4个,则需要考虑使用混合模式,解决策略类膨胀和对外暴露的问题。

2.13 适配器模式:

定义:将一个类的接口变成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

优点:

1. 适配器可以使原本不想关的两个类一起工作
2. 增加了类的透明性,
3. 灵活性好

注: 适配器模式最好在详细设计阶段不要考虑它,它不是为了解决还处在开发阶段的问题,而是解决正在服役的项目问题。

  • 通过对象层次的关联关系进行委托的,而不是继承关系,这种适配器叫对象适配器。

  • 通过继承进行的适配,称为类适配器。

2.14 迭代器模式:

定义:提供一种方法访问一个容器对象中各个元素,而又不用暴露该对象内部的细节。

迭代器类似一个数据库中的游标,可以在一个容器内上下移动,遍历所有它需要查看的元素。

从JDK 1.5 开始,在各种容器中都已经实现了这种模式的访问,所以对我们遍历某一个容器来说,并不需要重新实现这个过程。

2.15 组合模式:

定义: 将对象组合成树状结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

优点:

1. 高层模块调用简单
2. 节点自由增加

缺点:

1. 组合模式与依赖倒置原则冲突

2.16 *观察者模式:

定义:观察者模式也叫发布订阅模式。它定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。

优点:

1. 观察者被被观察者之间是抽象耦合
2. 建立一套触发机制

缺点:

1. 存在开发效率和运行效率的问题

注:

  • 广播连的问题:它和责任链模式最大的区别在于观察者广播连在传播的过程中消息是随时更改的,而责任链模式在消息传递过程中基本保持消息不变。
  • 异步处理问题:如果观察者比较多,而且处理时间比较长,就需要考虑采用异步处理,但是异步处理就要靠v了线程安全和队列的问题。(消息队列的作用)

2.17 门面模式:

门面模式也叫外观模式,是一种比较常用的封装模式。

定义: 要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。

优点:

1. 减少系统的相互依赖
2. 提高了灵活性
3. 提高安全性

缺点:

1. 不符合开闭原则

门面模式的核心就是封装。对于和外界系统交互来说,门面模式可以帮助我们提供给外界系统唯一的访问入口,同时它是稳定的,并不参与内部子系统的业务逻辑的变化。

2.18 *备忘录模式:

定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。

应用场景:

1. 需要保存和恢复数据相关状态场景
2. 提供一个可回滚的操作
3. 数据库连接的事务管理就是采用的备忘录模式

注意事项:

  1. 备忘录的生命周期:备忘录创建出来就要在最近的代码中使用,要主动管理它的生命周期,建立就要使用,不用就要立即删除其引用。
  2. 备忘录的性能:不要在频繁建立备份的场景中使用备忘录模式。

扩展:

  1. clone方式的备忘录:类似于原型模式,考虑到深拷贝和浅拷贝的问题,在复杂场景下,会让你的程序逻辑异常混乱,所以适用于比较简单的场景。
  2. 多状态的备忘录模式:
  3. 多备份的备忘录模式:

2.19 访问者模式:

定义: 封装一些作用于某中数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

优点:

1. 符合单一职责原则
2. 优秀的扩展性
3. 灵活性非常高

缺点:

1.具体元素对访问者公布细节
2.具体元素变更比较困难
3.违背了依赖倒置原则

2.20 状态模式:

定义: 当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。

状态模式的核心是封装,状态的变更引起了行为的变更。对外来说,我们只看到行为的改变,而不用知道是状态变化引起的。

优点:

1. 结构清晰:避免了过多的swtich...case或者if...else语句的使用
2. 遵循设计原则:很好的体现了开闭原则和单一职责原则
3. 良好的封装性

缺点:

1. 子类膨胀问题

应用场景:

1. 行为随状态改变而改变的场景
2. 条件、分支判断语句的替代者:通过扩展子类实现了条件的判断处理

2.21 解释器模式:

定义:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

一个简单的语法分析工具,扩展性好。

2.22 享元模式:

定义:使用共享对象可以有效的支持大量的细粒度的对象。 共享

优缺点:享元模式可以大大减少应用程序创建的对象(通过对象池缓存),降低程序内存的占用。但它也提供了系统复杂性,需要分离出内部和外部状态。

扩展问题:

  • 线程安全问题

2.23 桥梁模式:

定义:也叫桥接模式,将抽象和实现解耦,使得两者可以独立地变化

优点:

1. 抽象和实现分离
2. 优秀的扩充能力
3. 实现细节对客户透明

注意事项:

  • 考虑如何拆分抽线和实现
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

请我喝杯咖啡吧~

支付宝
微信