本文对JAVA中23种设计模式进行了简单的讲解,幷加以实际案例进行辅助理解,每种模式都会举例说明,幷将源码开源至gitee和githbu上。JAVA目录下为源码,resources目录下的UML文件夹中存放的是每种设计模式对应的UML源文件,本人使用Umbrello linux版。有错误的地方,欢迎大家指正。


前言

本文着重讲解了JAVA23种设计模式的概念,分析其中的角色,包括案例,幷画出案例设计类图供加深对设计模式的理解。每个模式都有代码实现,分别开源保存在github和gitee上,欢迎大家参考指点。


提示:以下是本篇文章正文内容,下面案例可供参考

一、设计模式的概念:

​ 设计模式是由有经验的软件开发人员总结出来的专门用来解决某一类特定问题的解决方案。这些解决方案是经过常时间的验证而总结出来的。它代表了解决这些特定问题的最佳实践。

二、设计模式的七大原则:

1、单一职责原则

​ 对于类而言,一个类应该只负责一个职责,如果一个A类负责两个职责:职责1和职责2,当职责1变动时,更改A类时可能会引起职责2发生改变。如果需要将A类拆分成两个类:A1类和A2类。

反面案例:

UserDao包含了对用户的增加获取操作,又包含了保存用户操作日志的操作,这违反了单一职责原则。
在这里插入图片描述
正面案例:

​ 对于反面案例的改进,将上述案例中UserDao中两个不相关的操作分离。实现每一个类都只做与自己相关的事情,使之满足单一原则。

在这里插入图片描述

2、接口隔离原则

​ 一个可对接口的依赖应该建立在最小接口依赖原则之上,也就是说,如果A类依赖B接口,且A类中只需要使用B接口的一个方法,那么B接口中其他方法对于A类来说就是冗余的。此时,要需要将B接口进行拆分成C接口和B接口,在C接口中创建A类中需要的那个方法,然后让A、B分别依赖C接口。

反面案例:

​ 对于接口LogDao中的四个方法,它的四个实现类中并不是每个方法都是必须的,比如,UserImpl类中只需要LogaDao中的saveInfo()方法而已,然而由于是实现了LogDao接口,那么他必须实现LogDao中的所有方法,由于saveLogInfoById()是LogImpl中的必须的实现的方法,这样间接的将LogImpl中的方法暴露给了UserImpl,这对于程序设计是不友好的,这样的设计违反了接口隔离原则。

在这里插入图片描述

正面案例:

​ 对于接口LogDao按照实现类中的需求,拆分为不同的接口,每个接口中定义对应实现类需要的方法,让对应的实现类去实现,这样就做到了接口隔离原则。

在这里插入图片描述

3、依赖倒转原则(依赖倒置原则)

  • 高层模块不能依赖底层模块,两者都应该依赖顶层。
  • 抽象不能依赖细节,细节应该依赖抽象。
  • 依赖倒转(倒置)的中心思想就是面向接口编程。
  • 依赖倒转(倒置)的设计理念是:抽象类出来的东西是相对稳定的东西,细节上的东西相对具多变性。以抽象为基础搭建的框架,要比以细节为基础的框架要稳定的多,在java中抽象指的就是抽象类或者接口。
  • 抽象类或者接口的目的就是制定好规范,但是却不涉及具体的操作,而是把细节上的实现交给它的实现类去完成。

4、里氏替换原则

  • 继承关系中,凡是引用父类的地方都可以使用子类来替换。
  • 继承关系中,子类尽量不要去重写父类已经实现了的方法,而是应该去重写父类中的抽象方法。
  • 在合适的情况下,应该使用聚合、组合、依赖的方式来替换继承。

5、开闭原则(ocp原则)

​ 开闭原则是最基础的原则,所有的原则都是为了实现了开闭原则而设计。

  • 模块或者函数应该对扩展(提供方)开放,对修改(使用方)关闭。

  • 在软件需求变化时,尽量通过扩展来实现,而不是通过修改已有的代码来实现。也就是用抽象构建框架,用实现来扩展细节。

6、迪米特法则(最少知道原则)

  • 一个类应该让其他对象对其保持最少了解原则,也就是说,该类应该提供一个public的方法,而不应该暴露自己是如何实现的。
  • 迪米特法则简单的说,就是只与其直接朋友通信。
    • 朋友:两个类之间有耦合关系,那么这两个类之前就存在朋友关系,耦合的分类有多种:依赖、关联、组合、聚合等。
    • 直接朋友:一个类如果出现在另外一个类的成员变量、方法参数、方法返回值等地方,那么这两个类就称为直接朋友关系。如果出现在局部变量位置,那么这个类就不是直接朋友关系,也就是说,局部变量最好不要出现在局部变量的位置。

7、合成复用原则

尽量使用合成或者聚合的方式,而不是使用继承的方式。

设计模式的七大原则总结:

  • 对程序中可变的地方独立出来,不要和不变的地方混杂在一起。
  • 面向接口编程,而不是面向实现编程。
  • 尽量让对象之间的设计满足低耦合,高内聚原则。

三、设计模式

​ 设计模式总共可以分为三种类型,共23种模式。

一 创建型模式(共4种):

1、单例模式

3.1.1.1、类变量写法

​ 如果确定这个实例一定会用到,那么这种写法是最完美的,不用担心线程安全问题。
优点:

  • 单例可以保证对象实例只有一个,减少内存开销。
  • 避免了对资源的多重占用。
  • 单例提供了一个全局的访问接口,可以优化对资源的访问。

缺点:

  • 单例没有实现抽象层,扩展很难,需要去更改源码。
  • 单例的代码逻辑都写在同一个类中,如果设计不合理,容易违背单一职责原则。
  • 如果一个单例程序没有执行完,那么就无法产生一个新的对象,对后续的业务会有影响。

应用场景:

  • 一个类如果需要频繁的去创建对象,使用单例模式可以减少系统的压力。
  • 如果某个对象实例需要不断的去创建,不断的去销毁,那么使用单例可以避免这种频繁的操作,比如:线程池,数据库链接。
  • 如果一个对象需要在多个场景下被使用,那么单例去共享该对象。减少内存消耗,加快对象的访问。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):StaticAttribute.java

github:StaticAttribute.java

3.1.1.2、静态内部类写法

​ 静态内部在外部类加载的时候,不会被加载,只有在调用getInstance()方法用到InnerClass时,才会被加载,做到了懒加载。其次类加载是线程安全的,所以也保证了是线程安全的。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):StaticInnerClass.java

github:StaticInnerClass.java

3.1.1.2、 DCL写法

​ DCL(双重检查锁)写法,是目前单例最常用的写法。好多地方都是用DCL实现的单例模式。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):DoubleCheckLock.java

github:DoubleCheckLock.java

2、工厂模式

​ 工厂模式将创建对象的方式对客户端隐藏起来,客户需要创建对象,只需要告诉工厂类对象的类型即可,关于对象是如果创建的,并不关心。工厂模式需要提供一个接口,由其子类实现该工厂接口,最终由子类来决定创建哪一个工厂类对象。

案例:

​ 假如画图功能,要求根据不同的类型,画出不同的图形。

应用场景:

  • 日志记录器。
3.1.2.1、简单工厂模式

​ 在工厂类内部进行列举判断,根据传入的对象的类型来获取到对应的工厂类的实例。

优点:

  • 简单工厂模式将工厂和产品进行分离,职责明确。
  • 客户端无需直到具体的产品名称,只需要直到产品的类型即可,就可以创建自己想要的产品。

缺点:

  • 简单工厂模式工厂类单一,职责过重,需要一系列的判断逻辑。一旦工厂类出现异常,则会导致整个系统瘫痪。
  • 扩展困难,如果需要增加一种产品,就必须修改工厂类代码。

应用场景:

  • 如果产品数量比较少,那么可以考虑使用简单工厂实现,只需要传入对应的产品类型即可创建出对应的产品,而无需知道具体的创建流程。

案例角色分析:

  • Shape:图形接口,定义图形的规则。
  • CircleShape/TriangleShape:图形接口的实现,定义了具体的图形。
  • ShapeFactory:创建图形的工厂类,决定创建哪种图形对象。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):simplefactorymodel

github:simplefactorymodel

3.1.2.2、工厂方法模式

​ 工厂方法模式将创建产品的任务下发到具体的产品对应的工厂类去实现。而最顶级的工厂类只是定义实现产品的规则,具体如何实现,交给产品对应的子类工厂去处理。

优点:

  • 客户端只需要知道产品的制造工厂名即可得到对应的产品。
  • 灵活性较强,增加一种产品,只需要增加一个工厂类即可。
  • 对外提供工厂类的抽象层,满足迪米特法则,里氏替换原则。

缺点:

  • 类的数量过多,增加系统的复杂度。
  • 一个工厂类只能产生一种产品。

应用场景:

  • 如果客户端只知道工厂的类名,而不知道具体的产品名。
  • 具体的产品必须由对应的产品工厂生产的情况。
  • 客户端不需要关注生产产品的具体细节,只关心具体的产品。

角色分析:

  • Product:抽象产品类,定义产品的规则。
  • ConcreteProduct:抽象产品类的实现类,具体定义产品的实现规则。
  • Factory:抽象工厂类,描述创建工厂的规则。
  • ConcreateFactory:抽象工厂类的实现类,定义创建某个产品的规则,一个实现类对应一种产品。

案例角色分析:

  • Shape:图形接口,定义图形的规则。
  • CircleShape/TriangleShape:图形接口的实现,定义了具体的图形。
  • ShapeFactory:创建图形的顶级工厂类,定义创建某个产品的规则。决定创建哪种图形对象。
  • CircleShapeFactory/TriangleShapeFactory:顶级工厂类的实现类,对应具体的某一个产品,由该实现类创建对应的产品。

类图:

在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):factorymethodmodel

github:factorymethodmodel

3.1.2.3、抽象工厂模式

​ 抽象工厂模式要求有一个顶级的抽象工厂类,定义获取工厂对象的规则。

优点:

  • 抽象工厂可以生产一个产品族的任何一个产品,从而减少了工厂类的实现。
  • 抽象工厂有很好的扩展性,当需要增加一个产品族的时候,只需要增加一个工厂类即可,满足开闭原则。

缺点:

  • 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

角色分析:

  • Product:抽象产品类,定义产品的规则。
  • ConcreteProduct:抽象产品类的实现类,具体定义产品的实现规则。
  • AbstractFactory:顶级抽象工厂类,描述创建工厂的规则。
  • ConcreateFactory:顶级抽象工厂类的实现类,定义创建某个产品的规则,一个实现类对应一种产品。
  • GenerateFactory:获取对应的具体工厂类对象。

案例角色分析:

  • Shape:图形接口,定义图形的规则。
  • CircleShape/TriangleShape/OtherShape:图形接口的实现,定义了具体的图形。
  • ShapeFactory/OtherFactory:创建图形的工厂类,定义创建具体对象的规则。
  • AbstractShapeFactory:顶级抽象工厂类,用于定义创建工厂的规则。
  • GenerateFactory:获取对应的具体工厂类对象。

类图:

在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):abstractfactorymodel

github:abstractfactorymodel

3、 原型模式

​ 原型模式用于解决创建重复的对象,同时又能保证性能。

优点:

  • 提高了性能,减少了创建对象的开销。
  • 避免了构造函数的约束。
  • 使用深拷贝可以保存原始对象的状态,对于需要恢复对象状态的场景下,可以实现对象状态的恢复。同样的,也可以辅助实现撤销功能。

缺点:

  • 克隆时使用的每个原始类需要实现Cloneable接口,并需要重写clone方法。
  • 对于嵌套使用的对象,每一层对象都需要支持深拷贝,这样增加了代码的复杂度。
  • 因为clone方法写在类内部,所有在更改代码的时候,需要修改原始类,违背了开闭原则。

应运场景:

  • 如果系统中存在大量的相同的相同或者相似的对象的创建的时候。

实现原理:

  • 浅拷贝–重写clone()方法

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):shallowcopy

github:shallowcopy

  • 深拷贝–序列化/反序列方法

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):deepcopy

github:deepcopy

4、建造者模式

建造者模式通过一步一步将多个简单的对象构建成一个复杂的对象。

优点:

  • 建造者模式将建造过程和表示分离。
  • 封装性好,利于扩展。各个建造者之间相互独立,有利于系统解耦。
  • 客户端不需要知道产品的构造细节,建造者模式一步一步利用细节将产品构造出来,有利于对细节风险的把控。

缺点:

  • 使用建造者模式生产的对象,必须保证产品之间有相同的组成部分,有一定的限制条件。
  • 如果产品本身很复杂,需要改动的时候,建造者也需要改,维护成本大。

应用场景:

  • 当系统内部有许多类的构建是非常复杂的时候,这些类需要一步一步的构建出来,这时可以将相同的部分抽象出来,使用建造者模式构建对象。建造者模式将不变与变分离出来,这样可以做到灵活选择。

案例:

​ 假如实现汉堡套餐的制作。

案例角色:

  • Hamburg:汉堡的抽象层定义。
  • Drink:饮料的抽象层定义。
  • KFCBuilder: 汉堡套餐的抽象定义。
  • BurgerPackage:汉堡套餐的定义。
  • KFCDirector:汉堡套餐的指挥者,用于指定套餐的制作流程。
  • BeefPepsiKFCBuilder/ChickenCokeKFCBuilder:牛肉百事可乐汉堡套餐建造者,鸡肉可口可乐汉堡套餐建造者。
  • ChickenHamburgers/BeefHamburgers:具体的汉堡实现,鸡肉汉堡牛肉汉堡。
  • Coke/Pepsi:具体的饮料实现,可口可乐百事可乐。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):builderpattern

github:builderpattern

二 结构型模式(共8种):

1、适配器模式

​ 适配器模式为两个不兼容的接口之间搭建桥梁。这种模式需要一个类来结合两个不兼容的接口的功能,该类通过增加一个方法使得原本不能兼容或者不能协同工作的接口可以协同完成某一个任务。举个例子:内存卡是不能直接放在笔记本上使用的,那么通过读卡器,就可以使用笔记本读取内存卡。

优点:

  • 可以在不改变原始类的情况下,就能将原始类改变成目标类的样子,实现了代码复用。
  • 使用适配器类,能很好的解决目标类和原始类不一致的问题。
  • 适配器模式一般情况都符合开闭原则。

缺点:

  • 适配器模式增加了系统的复杂度,有时候很难理解这种模式下的代码。

应用场景:

  • 需要将原有的接口和现有的接口对接,二现有的接口与原有的接口不一致的时候。

  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。

  • 角色分析:

    Source:需要被适配的类、接口、对象。
    Destination:需要得到的类,Source通过适配得到的类对象,也就是我们期待得到的对象。
    Adapter:适配器类,协调Source和Destination,使两者能够协同工作。

  • 案例:

​ 假如目前手机需要充电,但是家用插座的电压22V,而手机需要的电压5V,那么可以使用手机充电器进行电压转化,得到手机需要的电压5V。

  • 案例角色

    PhoneVoltage:我们期望得到的接口。

    HouseholdVoltage:被适配的对象。

    PhonePharger:适配器。

3.2.1.1、类适配器

​ PhonePharger需要继承HouseholdVoltage类,实现HouseholdVoltage向PhoneVoltage的转化。

类图:

在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):classadapter

github:classadapter

3.2.1.2、对象适配器

​ 对象适配器是类适配器的一种改进,按照合成复用原则的要求,尽量使用聚合的方式来替代继承方式。所以只是将HouseholdVoltage和PhonePharger的继承关系换成了聚合的关系。即PhonePharger内部持有HouseholdVoltage的对象实例。

类图:

在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):objectadapter

github:objectadapter

3.2.1.3、接口适配器

​ 接口适配器就是指,如果一个接口中有太多的方法,而适配器类又不需要实现那么多的方法,那我们可以在适配器类和接口中间加一个抽象类,用抽象类空实现接口中的所有的方法,然后配器类通过重写抽象类中的需要的方法来进行处理。
​ PhonePharger需要聚合HouseholdVoltage类,而不再是继承。也不是直接实现PhoneVoltage接口,而是在两者之间增加抽象类AbstractVoltage,AbstractVoltage抽象类空实现PhoneVoltage接口的所有方法,由子类去继承抽象类AbstractVoltage,幷选择性的实现需要的方法,而不是实现全部方法。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):interfaceadapter

github:interfaceadapter

2 、桥接模式

​ 桥接模式将抽象层和实现层分开,使得二者可以独立变化。在桥接模式中,最关键的部分是抽象层依赖于实现层。

优点:

  • 抽象与实现层分离,扩展性好。
  • 符合开闭原则,合成复用原则。
  • 实现了将细节对用户透明的原则。

缺点:

  • 由于聚合在抽象层出现,需要对抽象层进行合理的设计。

应用场景:

  • 出现大量的继承的情况下,可以考虑使用桥接模式来替代。

案例:

​ 假如有一个画图功能,有图形的抽象定义和画图的接口实现两部分组成。

角色分析:

  • Abstraction:抽象层接口,定义抽象层角色的属性规则。
  • RefinedAbstraction:抽象层接口的实现,重新定义了抽象层角色的属性规则。
  • Implementor:实现层接口,定义实现层角色的行为规则。
  • ConcreteImplementor:实现层接口的具体实现。重新定义了实现层角色的行为规则。

案例角色:

  • Shape:图形的抽象层定义。
  • RefinedShape:图形的抽象层定义的实现,重新定义了图形的行为规则。
  • DrawShape:画图行为的接口定义。
  • CircleDrawShape/TriangleDrawShape:画图行为的接口定义的具体实现,分别实现了画圆形和三角形。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):bridgingpattern

github:bridgingpattern

3、装饰者模式

​ 装饰者模式表示,动态在已经存在的对象上增加新的功能,而不影响原有的功能。

优点:

  • 装饰者模式对继承进行了很好的补充,在不需要更改原有对象的前提下,可以实现对一个类的扩展,完全符合开闭原则。
  • 可以使用装饰者类进行不同的排列组合,实现不同的装饰效果。

缺点:

  • 装饰者模式需要增加许多的子类,使得程序会变得更加庞杂。

应用场景:

  • 对象功能支持增加和撤销的情况下,可以使用装饰者模式实现。
  • 需要使用不同的装饰类的排列组合,来实现丰富的功能的时候。

案例:

​ 假如顾客在点饮料的时候,可能需要加料,比如:牛奶、红豆、水果。

角色分析:

  • Decorator:装饰者。
  • Component:抽象层接口,就是各种单品实体。
  • ConcreteComponent:具体的实体。

案例角色:

  • Drink:饮料,抽象层,装饰者和被装饰者都需要继承。
  • MilkyTea/Coke/ Juice:奶茶/可乐/果汁,具体的饮料,角色为被装饰者。
  • FoodstuffDecorator:食料,装饰者抽象层。
  • MilkDecorator/OrmosiaDecorator/FruitDecorator:牛奶、红豆、水果,具体的食料,角色为装饰者。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):decoratorpattern

github:decoratorpattern

4、组合模式(部分整体模式)

组合模式表示,是一个创建对象树形结构的模式,它表示对象的整体与部分的关系。

优点:

  • 组合模式可以统一的处理对象,无论是单个对象还是组合对象。
  • 很容易在组合对象中增加新的对象,而不需要更改原代码。
  • 很容易实现树形结构逻辑。

缺点:

  • 组合对象的设计比较复庞杂,需要很大的精力分清组合对象关系。
  • 不容易限制容器中新增对象。

应用场景:

  • 需要表示一个整体和部分关系结构场景下。

案例:

​ 以学校的组织结构为例,大学下边设有学院,学院下边设有系。

案例角色:

  • Organization:组织。
  • Department:院系。
  • College:学院,组合院系。
  • University:大学,组合学院。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):compositepattern

github:compositepattern

5、外观模式

​ 外观模式表示,在所有的实现层增加一个统一的对外访问接口,客户端只需要通过这个统一的接口即可得到自己想要的功能。

优点:

  • 外观模式将客户端和子系统进行解耦,使得子系统的变化不影响对客户端的响应。
  • 外观模式屏蔽了子系统对客户端的组件。

缺点:

  • 如果增加了子系统,那么外观类也需要逻辑修改,违背了开闭原则。
  • 无法限制客户端对子系统的使用。

应用场景:

  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

案例:

​ 画图功能。

案例角色:

  • Shape:图形接口。
  • TriangleShape/CricleShape/RhombusShape:Shape的实现类,具体的图形。三角形、圆形、菱形。
  • ShapeMaker:外观类,客户端只需要访问这个外观类就可以画出自己想要的图形。

类图:

在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):facadepattern

github:facadepattern

6、享元模式

​ 享元模式表示,主要用于减少对象的创建,提高性能和较少内存开销而设计。享元模式尝试重用现有的同类对象,如果没有,则再去创创建新的对象。

优点:

  • 相同的对象只需要保存一份,使用的时候,只需要获取即可,大大减少了创建对象带来的内存消耗,也减少了对象的数量。

缺点:

  • 享元模式为了方便对象共享,需要将一些不相同的对象外观化,增加了系统的复杂性。

应用场景:

  • 系统中出现大量的相同或者相似的对象,应用享元模式可以减少系统的内存消耗。
  • 由于享元模式需要维护一个存放对象的集合,所以大量的对象才适用于享元模式。

案例:

​ 画图功能。

案例角色:

  • Shape:图形接口。
  • CricleShape:Shape的实现类,具体的图形。圆形,但是可以画出不同大小的圆形。
  • ShapeFactory:图形工厂,主要用于获取图形对象。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):fyweightpattern

github:fyweightpattern

7、代理模式

代理模式表示,一个类代表另外一个类,并能完成该类的某个功能,且还能在该功能上做一些扩展。

优点:

  • 代理模式通过代理的方式,保护了并扩展了目标对象。

缺点:

  • 增加了系统的复杂度。
  • 代理模式会导致类的数量增加。

应用场景:

  • 为了隐藏实际目标,可以使用代理模式构造一个代理类暴露给客户端。

案例:

​ 有一个业务模块的方法(login())已经运行了很久了,要求在不更改源代码的前提下,给这个这个方法执行前后增加日志打印。

案例角色:

  • Login:Login是一个接口。
  • ModelALogin:实现了Login接口。
  • ProxyModelALogin:ModelALogin的代理类,会在执行ModelALogin的login()的前后增加日志打印。
3.2.7.1、 静态代理模式

类图:

在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):staticproxy

github:staticproxy

3.2.7.2、 JDK动态代理模式

类图:

在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):jdkproxy

github:jdkproxy

3.2.7.3、 CGLIB动态代理模式

类图:

在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):cglbproxy

github:cglbproxy

8、过滤器模式

过滤器模式表示,允许开发人员定义不同的规则来过滤同一组对象,通过逻辑运算以解耦的方式把它们连接起来。

优点:

  • 过滤器模式将过滤规则和对象集合分离出来,降低了系统的复杂度。
  • 过滤器中过滤规则可以复用。

缺点:

  • 对同一组对象进行多次过滤,影响系统的性能。如果有n个对象的集合,过滤了m次,那么复杂度为o(mn)。

应用场景:

  • 拦截器。

案例:

​ 假如有一些不同形状的不同颜色不同大小的气球,现在可能每个场景需要各自的气球,那么需要将这些气球按照不同的场景分离出来。

案例角色:

  • Balloon:气球类。
  • Filter:过滤器接口。
  • ColorBalloonFilter/SizeBalloonFilter/ShapeBalloonFilter:Filter的实现类,具体的过滤器。
  • BalloonFactory:气球工厂,主要用于产生气球。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):filterpattern

github:filterpattern

三 行为型模式(共11):

1、模板模式

模板方法表示,抽象类中公开定义一系列的方法,留在子类中去实现,并且,规定了这些方法的执行顺序,子类是不能去更改执行顺序的,但是可以重写方法的具体实现。

优点:

  • 模板模式将不变的部分和变的部分分离出来,可变的部分由子类实现,便于扩展,符合开闭原则。

缺点:

  • 模板模式大量使用继承的方式实现,由于继承的缺陷,如果抽象类改变了,子类也需要跟着改变。
  • 可变的部分可能会被多个子类实现,增加了系统的复杂性。

应用场景:

  • 当多个子类中存在的相同的部分时,可以考虑使用模板模式将子类中相同的部分抽取出来。

角色分析:

  • Abstract:抽象类,抽象层,公开定义一系列的方法,并且,规定了这些方法的执行顺序。
  • Concreator:抽象层的实现类,重写了方法的具体实现。

案例:

比如吃烧烤,分为生火,准备食材,烧烤,享用美食,打扫。

类图:

在这里插入图片描述

案例角色:

  • AbstractBarbecue:定义烧烤食物的方法和过程。
  • RoastedSweetPotatoes/RoastMeat:继承AbstractBarbecue,具体的烧烤食物的实现。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):profilepattern

github:profilepattern

2、命令模式

命令模式表示,将发布命令和接收命令的对象之间进行解耦,该模式中,会将一个命令封装到一个对象中,根据不同的参数来表示不同的命令请求,命令模式要求必须有命令的执行和撤销功能。

优点:

  • 命令模式采用接口变成,扩展性良好,增加删除命令非常容易,并且不影响其他的命令类,满足开闭原则。
  • 命令模式中方便实现undo和redo命令,可以结合备忘录模式实现撤销与恢复功能。

缺点:

  • 可能会产生大量的命令类。因为每一个具体的操作都需要实现一个具体的命令类,这会增加系统的复杂性。

应用场景:

  • 请求者和调用者需要解耦的时候,可以使用命令模式,实现调用者和请求者互不干扰。
  • 系统中有一系列的命令的需要的处理的时候,可以使用命令模式来处理。
  • 系统中需要支持撤销和恢复功能的时候,可以容易的使用命令模式实现。

角色分析:

  • Invoker:调用者角色,用来发布命令。
  • Command:命令角色,是一个抽象层,所有能执行的命令都在这里定义。
  • ConcreateCommand:具体的命令角色,实现了Command抽象层,将命令和具体的行动绑定起来。
  • Receiver:命令接收者,知道该如何执行这个命令。

案例:

假如万能遥控器,能操作电灯,电视,空调,支持开关功能。

类图:
在这里插入图片描述

案例角色:

  • UniversalRemoteControl:万能遥控器,相当于调用者。
  • Command:命令接口,定义能执行的必须的操作,供其他命令类去实现。
  • LightOnCommand/LightOffCommand:电灯的命令实现类。
  • NoCommand:撤销命令实现,对于空命令,做一个统一的处理类。
  • LightRecever:电灯的命令接受者。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):commandpattern

github:commandpattern

3、访问者模式

访问者模式表示,在不破坏原来对象的数据结构的前提下,使用新的对象去改变该对象的执行算法,从而对数据结构定义新的执行算法。

优点:

  • 扩展性好,能在不改动源对象的数据结构的情况下,对源对象的数据结构进行修改或者增加操作。
  • 符合单一职责原则,访问者模式将所有的对源对象的数据结构的操作封装成访问者类。

缺点:

  • 破坏了封装性,使得对象的内部结构被公开。
  • 访问者模式没有使用抽象层,违背了依赖倒转原则。

应用场景:

  • 对象结构稳定,但是其操作算法需要经常变动的情况下。

案例:

软件包升级

案例角色分析:

  • HardWare:硬件接口,软件包升级时,通过新的算法对硬件进行修复。
  • Visitor:访问者接口。
  • UpdatePackagesVisitor:实现了Visitor接口,软件升级包访问者。
  • Robot:机器人类。

类图:
在这里插入图片描述

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):visitorpattern

github:visitorpattern

4、迭代器模式

迭代器模式表示,对于不同的数据结构访问,提供一个统一的遍历接口,不关心底层是如何实现的,都能统一遍历数据。

优点:

  • 客户端不需要关注对象中的聚合是怎么实现的,只需要访问该聚合对象即可。
  • 支持提供一个统一的迭代接口,但是确可以迭代各种聚合对象。

缺点:

  • 会增加类的数量,因为每一种聚合方式基本上都要实现一种迭代方法。

应用场景:

  • 当对聚合对象需要一系列的迭代方式时。
  • 当不需要暴露聚合对象中的内部细节时。

角色分析:

  • Iterator:Java提供的一个迭代器接口。
  • ConcreateIterator:实现Iterator接口的实现类,具体的实现每一个数据结构的遍历方式。
  • Aggregate: 创建对应的迭代器的接口。
  • ConcreateAggregate:Aggregate具体的实现。

案例:

假如学校下边信息工程系下边有学院:信工学院,金融学院。信工学院下边有系:信息工程系、网络安全系,金融学院下边有系:金融系、会计系。要求使用迭代器模式打印学校的组织结构信息

类图:
在这里插入图片描述

案例角色:

  • Iterator:Java提供的一个迭代器接口。
  • InstituteScienceTechnologyIterator/SchoolFinanceIterator:信工学院/金融学院对应的迭代器。
  • DepartmentInformationEngineering/NetworkSecurityDepartment:信息工程系/网络安全系。
  • DepartmentFinance/DepartmentAccounting:金融系/会计系。
  • InstituteScienceTechnology/SchoolFinance:信工学院/金融学院。
  • College: 提供一个获取迭代器的方法。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):iteratorpattern

github:iteratorpattern

5、观察者模式

观察者模式表示,观察者模式是一个一对多的模型结构,一个对象中的数据结构有变更时,需要通知其他依赖它的对象作出响应。

优点:

  • 降低了目标类和观察者类的耦合度,符合依赖倒转机制。
  • 目标类和观察者类之间搭建了一个触发机制。

缺点:

  • 目标类需要通过一系列的操作才能触发观察者的某个操作。当观察者比较多的时候,需要花费大量的资源实现。

应用场景:

  • 需要实现广播机制的时候,可以使用观察者模式实现。
  • 对象之间存在一对多的关系的时候,其中一个对象的改变,可以影响多个对象的情景下。

角色分析:

  • SubObject:观察者模式的决定者。
  • Observers:下边依赖SubObject的观察者,当SubObject发生变动时,Observers需要作出响应。

案例:

天气预报站将天气信息公布出来,并通知其他网站及时更新天气信息数据。

案例类图:
在这里插入图片描述

案例角色:

  • Weather:天气接口。
  • WeatherStation:气象站,实现Weather,并提供一个注册观察者的方法。
  • Observer:观察者抽象层。
  • SinaWebsiteObserver/TencentWebsiteObserver:新浪网站,腾讯网站,需要注册的观察者。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):observerpattern

github:observerpattern

6、中介者模式

中介者模式表示,降低了对象与对象之间的耦合度,多个对象之间的通信由一个中介对象来负责调度完成。

优点:

  • 中介者模式将各个类进行分工,使得各个类各司其职,符合单一原则。
  • 中介者模式中,类的实现只对中介者开放,对用户的关闭的,符合迪米特法则。
  • 使得类与类之间通过中介者类由多对多的关系转变成了一对一的关系。

缺点:

  • 当所有的同事类,都去依赖中介者类的时候,会使得中介者类比较臃肿。

应用场景:

  • 当想创建一个类去协调多个类之间的关系的时候。
  • 当对象之间太过复杂的时候,可以使用中介者模式进行解耦。

角色分析:

  • Mediator:中介者抽象层。
  • ConcreateMediator:具体的中介者实现,它会管理所有的同事类。
  • Colleague:同事类抽象层。
  • ConcreateColleague:具体的同事类,每个同事类只知道自己的行为。

案例:

聊天室。

案例类图:

在这里插入图片描述

案例角色:

  • ChatRoom:聊天室,中介者,用来调度处理用户发送的消息。
  • User:用户层。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):mediatorpattern

github:mediatorpattern

7、备忘录模式

备忘录模式表示,这种模式将一个对象的状态记录在外部对象上,在适当的时候,通过这个外部对象来恢复对象之前的状态。

优点:

  • 可以提供恢复机制,能很方便的实现数据会到恢复到某一个历史阶段。
  • 备忘录模式中,不需要管理和保存各个数据的备份,因为这些都交给备忘录处理,符合单一职责原则。

缺点:

  • 资源消耗很大,因为如果需要备份的数据的状态比较多,会占用较多的内存资源。

应用场景:

  • 需要保存数据状态的场景的时候,比如游戏中的某个中间状态。
  • 需要提供一个回滚操作的场景的时候,比如记事本的撤销功能。

角色分析:

  • Memento:备忘录对象。
  • Originator:原始对象。
  • CareTaker:用来管理备忘录对象,因为可能备忘录对象是记录了多个原始对象的对象。

案例:

游戏角色。

案例类图:
在这里插入图片描述

案例角色:

  • GameMemento:GameOriginator游戏角色的备忘录对象。
  • GameOriginator:具体的一个游戏角色。
  • GameCareTaker:GameMemento的管理者,聚合了多了多个GameMemento对象。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):mementopattern

github:mementopattern

8、解释器模式

解释器模式表示,这种模式下,定义一种特定的语言解析方式,用来解释特定语言的语法规则。

优点:

  • 扩展性良好,由于解释器的解释类是通过继承来实现,所以能通过继承来扩展语法。
  • 容易实现,由于语法树中的语法都是相似的,所以一组语法解析实现起来都是比较方便的。

缺点:

  • 语法树中的每一个语法都需要定义一个类,会导致类膨胀。
  • 执行效率低下,应为解释器中一般都是循环解析某一个语法。

应用场景:

  • 如果一个语法需要解释执行时,需要采用解释器模式。
  • 当系统中存在需要进行解析的语法树时,且不需要关注执行效率的时候。

案例:

比如要sql语句的解析。

案例类图:
在这里插入图片描述

案例角色:

  • Expression:Expression是一个接口,定义解释器的接口方法。
  • SQLAExprssion/SQLBExprssion:实现了Expression接口,定义了不同的语法解析规则。
  • Context:语法解析用到的辅助的类。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):interpreterpattern

github:interpreterpattern

9、状态模式

状态模式表示,这种模式下,一个状态下对应该状态下的行为,也就是说,再改状态下,只能执行该状态允许的动作。

优点:

  • 结构清晰,状态模式将结构和不同的行为结合起来,满足单一职责原则。
  • 状态模式职责清晰,有利于程序扩展,很容易通过增加子类的方式增加新的状态。

缺点:

  • 状态模式下,会增加类的数量增加以及创建对象的增加。
  • 状态模式对开闭原则支持的不是很好,只有增加新的状态才能切换符新的状态,否则无法完成。

应用场景:

  • 当对象的行为与对象的状态有关时,并且在运行时,对象的行为会根据状态改变的时候。
  • 一个操作中含有庞大的分支结构,并且这些分支结构会取决于对象的状态时。

角色分析:

  • State:状态接口,定义一部分操作。
  • ConcreateState:具体的状态对象实现。
  • Context:聚合了State实例对象。

案例:

窗口叫号办理业务。

案例类图:

在这里插入图片描述

案例角色:

  • Context:聚合了State,客户端使用context获取当前状态下的信息。
  • State:状态接口。
  • ToBeCalledState/CalledState/EndCalledState:实现了State接口,定义具体的状态,并且指定该状态下具体行为。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):statepattern

github:statepattern

10、策略模式

  • 状态模式表示,一个类的行为,在运行时可以改变,在这种模式下,可以创建多个策略对象,一个类的行为可以根据不同的策略对象而改变。

优点:

  • 策略模式很容易的实现一种可重用的算法簇,为相同的行为做出不同的实现,客户端可以随意的更改算法。
  • 策略模式将算法实现转移到策略类中,算法的使用放在环境类中,实现了二者的分离。
  • 策略模式对开闭原则完美支持,可以在不修改源代码的情况下,只增加新的算法策略即可。

缺点:

  • 客户端必须非常了解算法策略才能在使用时选择恰当的算法策略。

应用场景:

  • 系统中各种算法相互独立,但是需要在不同的运行时期转化不同的策略的时候,而且需要对外隐藏算法实现细节时。

  • 一个对象有多种算法的时候,并且需要在多种算法中选择一种的时候,可将这些算法封装在策略类中。

    角色分析:

    • Strategy:策略接口,定义一部分操作。
    • ConcreateStrategy:实现了Strategy接口,具体的策略对象实现。
    • Context:聚合了Strategy实例对象。

    案例:

    猫比较大小,可以根据体重比较大小,也可以根据身高比较大小。。

    案例类图:

在这里插入图片描述

案例角色:

  • Context:聚合了Strategy,客户端使用context获取不同的策略对象。
  • Strategy:策略接口。
  • WeightStrategy/HeightStrategy:体重比较策略,身高比较策略,实现了Strategy接口,定义具体的比较大小的策略。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):strategypattern

github:strategypattern

11、责任链模式(职责链模式)

责任链模式表示,这种模式下,一个对象中保存了下一步处理该请求的对象实例,当这个对象无法处理这个请求的时候,就会将该请求发送下一个对象。

优点:

  • 责任链模式职责模式明确,每个类都只负责自己的职责,处理不了的就往下传递,符合单一直则原则。
  • 责任链模式中每一个类都保留了一个指向后续一个类的指向,如果职责连关系发生变化,那么只需要调换每个类中的指向即可,满足开闭原则。
  • 系统扩展性好,如果有新的处理类,那么只需要在责任链中增加该处理类即可。
  • 责任链模式中,维护着一个链式结构,客户端不需要知道链中的结构以及链式是如何连接的。

缺点:

  • 责任链模式中每一个请求是无法保证都被正确处理,可能会出现请求无法处理的情况。
  • 责任链模式中,如果维护的链比较长, 那么就会造成请求经过一个很长的过程才能得到结果。

应用场景:

  • 一个请求可能只有被多个对象来处理的才能得到结果的时候,但是具体是哪个对象只有在运行时才能确定。
  • 在不需要明确处理者的前提下,可以将请求交给一堆处理者的时候。

角色分析:

  • Handler:请求处理类接口,定义一部分操作。
  • ConcreateHandler:处理请求的具体实现者。

案例:

请假流程,如果请假时间超过三天项目经理需要提交给部门经理处理,如果部门经理处理不了,需要提交给地域经理处理,依次类推,问题处理之后返回处理结果。

案例类图:
在这里插入图片描述

案例角色:

  • Handler:请求处理者接口。
  • ProjectManagerHandler/HoDHandler/TerritoryManagerHandler/CEOHandler:项目经理处理这者,部门经理处理者,地域经理处理者,CEO处理者,具体的请求处理者实现。
  • Context:聚合Handler。

代码地址(CASE.md为该模式的设计说明,请先阅读,再看代码):

gitee(码云):responsibilitychainpattern

github:responsibilitychainpattern

总结

本文对JAVA中23种设计模式进行了简单的讲解,幷加以实际案例进行辅助理解,每种模式都会举例说明,幷将源码开源至gitee和githbu上。JAVA目录下为源码,resources目录下的UML文件夹中存放的是每种设计模式对应的UML源文件,本人使用Umbrello linux版。有错误的地方,欢迎大家指正。

Logo

更多推荐