23种设计模式

创建型模式

创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离.这样可以降低系统的耦合度,使用者不需要关注对象创建的细节,对象的创建由相关的工厂来完成.就像我们去买商品时,不需要知道商品是怎么生产出来一样,因为它门由专门的厂商生产.

创建型设计模式分为以下几种

  • 单例(Singleton)模式:某个类智能生成一个实例,该类提供了一个全局访问点供外部获取该类实例,其拓展是有限多利模式
  • 原型(prototype)模式:将对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例
  • 工厂方法(FactoryMethod)模式:定义一个用于创建产品的接口,由子类决定生产什么产品
  • 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以产生一系列相关的产品
  • 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建他们,最后构建成该复杂对象

以上5种创建型模式,除了工厂方法模式属于类创建型模式,其他的全部属于对象创建型模式.

单例模式

单例(Singleton) 模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式.

例如windows中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口造成内存资源的浪费,或出现各个窗口显示内容不一致等错误

javaEE标准中的servletContext和ServletContextConfig,Spring框架中的ApplicationContext,数据库中的连接池都是单例模式

单例模式有三个特点

  1. 单例类只有一个实例对象
  2. 该单例对象必须由单例类创建
  3. 单例类对外提供一个访问该单例类的全局访问点

单例模式的优点和缺点

优点

  • 单例模式可以保证内存里只有一个实例,减少了内存的开销
  • 可以避免对资源的多重占用
  • 单例模式设置全局访问点,可以优化和共享资源的访问

缺点

  • 单例模式一般没有接口,扩展困难,如果要扩展,除了修稿原来的代码,没有第二周途径,违背开闭原则
  • 在并发测试中,单例模式不利于代码调试,在调试中,如果单例中的代码没有执行完,也不能模拟生产一个新对象
  • 单例模式的功能通常卸载一个类中,如果功能设计不合理,容易违背单一指责员职责原则

实现形式

单例模式通常有俩种实现形式

懒汉单例:该模式的特点是类加载时没有生成单例,只有第一次调用getInstance方法时采取创建这个单例

饿汉单例:一旦加载就创建一个单例,保证在调用getInstance方法之前单例就已经存在了

原型模式

原型(protype)模式定义如下:用一个已经创建的实例作为原型,通过复制原型对象来创建一个核原型相同或相似的对象

这里原型实例指定了要创建的对象种类.用这种方式创建对象非常高效,根本无需知道对象的创建细节.

优点

  • java自带原型模式基于内存二进制流的复制,在性能上比直接new一个对象更加优良
  • 可以使用深克隆的方式保存对象的状态,使用原型模式讲对象复制一份,并将其状态保存起来,建华路创建对象的过程,以便在需要的时候使用

缺点

  • 需要每一个类都配置一个clone方法
  • clone方法位于类的内部,对已有的类进行改造的时候需要修改代码,违背了开闭原则
  • 深克隆的时候,对象的多层嵌套,导致每一层对象对于的类都必须支持深克隆,实现起来会比较麻烦

简单工厂模式

该模式不属于Gang of Four(GoF)的23种经典设计模式,但它是一种常见且简单的创建对象的方式。又称静态工厂方法模式

工厂模式的定义:一个创建产品对象的工厂接口,将产品对象的实例创建工作推迟到具体子工厂类当中

例如spring中创建对象可以传入class对象然后按照类型进行注入,这就是简单工厂模式.

简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。

这满足创建模型中所要求的"创建与使用相分离"的特点

按照业务场景划分,工厂模式有三种不同的实现方法,分别是简单工厂模式,工厂方法模式和抽象工厂模式

我们把被创建的对象称为产品,把创建产品的对象称为工厂.如果要创建的产品不多,只需要一个工厂类就可以完成

在简单工厂模式创建实例的方法通常为静态方法,因此简单工厂模式(Simple Factory Pattern),又叫做静态工厂方法模式(Static Factory Method Pattern)

简单来说,简单工厂模式有一个具体的工厂类,可以生产多个不同的产品,属于创建型设计模.简单工厂模式不在GoF23种设计模式之列

简单工厂模式每增加一个产品就要增加一个具体产品类和一个对月的具体工厂类,这增加了系统的复杂度违背了开闭原则

优点

  • 工厂必须包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例.客户端可直接免除创建产品对象的职责,很方便的创建出相应的产品.工厂和产品的职责区分明确.
  • 客户端无需知道创建具体的类名,只需要知道参数即可
  • 可以引入配置文件,在不修改客户端代码的情况下更huan和添加新的具体产品类

缺点

  • 简单工厂模式的工厂类单一 ,负责所有产品的创建,职责过重,一旦异常震哥系统将受影响。而且工厂类代码会非常臃肿,违背高聚合原则
  • 使用简单工程模式会增加系统中类的个数,增加系统复杂度和理解难度
  • 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型多的时候,逻辑困难过于复杂
  • 简单工厂模式使用了static工厂方法,造成工厂角色无法基于继承等级结构

工厂方法模式

工厂方法模式,是对简单工厂模式的进一步抽象化

创建一个工厂接口和创建多个工厂实现类,这样一旦需要增加新功能,直接增加新的工厂类就可以了,不需要修改之前的代码。

好处是开源使系统在不修改原来代码的情况下引入新产品,满足开闭原则。

优点

  • 用户只需要知道具体工厂的名称就可以得到所要的产品,无需知道创建过程
  • 灵活性增强,对于新产品的创建,只需写一个响应的工厂类
  • 解耦框架,高层模块只需要知道产品的抽象类,无需关心其他的实现类,满足迪米特法则,依赖倒置原则和里氏替换原则

缺点

  • 类的个数容易过多,增加复杂度
  • 增加了系统同的抽象性和理解难度
  • 抽象产品只能生产一种产品,弊端可以使用抽象工厂模式解决

具体实现

  1. 抽象工厂:提供了创建产品的接口,调用者通过它访问具体的工厂方法newProduct()来创建产品
  2. 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建
  3. 抽象产品,定义了产品规范,描述了产品的主要特性和功能
  4. 具体产品:实现了抽象角色锁定义的接口,由具体工厂来创建,它与具体工厂之间一一对应

抽象工厂模式

定义:是一种为访问类提供一个创建一组相关活互依赖对象的接口,并且访问类无需指定所需要产品的具体类就能得到同组的不同等级的产品的结构模式

抽象工厂模式是工厂模式的升级版本,工厂法法模式只生产一个等级的产品,而抽象工厂模式

抽象工厂模式是工厂法法模式的升级版本,工厂方法模式只能生产一个等级的产品,而抽象工厂模式可以生产多个等级的产品

本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,图 1 所示的是海尔工厂和 TCL 工厂所生产的电视机与空调对应的关系图。

使用抽象工厂模式一般满足一下条件

  • 系统中有多个产品族,每个具体更通畅创建同一族但不同等级结构的产品
  • 系统一次只可能消费某一组的产品,即同组茶农的一起使用

角色:

  1. 抽象工厂:提供了创建产品的接口,包含多个创建茶农的方法,可以创建多个不同的产品等级
  2. 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体的产品创建
  3. 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品
  4. 具体产品:实现了抽象角色定义的接口,具体工厂来创建,他们同具体工厂是一对一的关系

优点

  • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理
  • 当需要产品族时,抽象工厂可以保证客户端始终使用同一个产品的产品组.抽象工厂增加了程序的可扩展性,当增加一个新的产品族时不用修改代码

其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。

工厂方法模式,简单来说是来为了解决简单工厂模式所出现的一些缺点而进行的改进。比如当一个简单的工厂模式要生产一个飞机。能够生产直升机,战斗机,波音747.但是如果要新增一个水上飞机。那么就要修改源代码,也就是修改工厂的源代码!添加一个业务逻辑,显然不符合开闭原则,所以就有了工厂方法模式。提供一个抽象工厂方法模式,这样就可以避免新增的时候修改源代码,只要新建一个类来继承了工厂方法模式即可。

比如spring在工厂中添加新的类,不需要修改spring源码,这就是抽象工厂模式的实现.

建造模式

指将一个复杂对象的构造与它的表示分离,使用同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式.

它是将一个复杂的对象分解为多个简单的对象,然后一步步构建而成.它将变与不变相分离,产品的组成部分是不变的,但每一部分是可以灵活选择的

结构

  • 产品角色:包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件
  • 抽象建造者:一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回产品的方法
  • 具体建造者:实现builder接口,完成复杂茶农的哥哥部件的具体方法
  • 指挥者它调用建造者对象中的部件构造与装配方法完成复杂的对象创建,在只会者中不涉及具体的产品信息

比如lombok的@Builder就是建造者模式

StringBuilder符合建造者模式的思想,但与传统的建造者模式略有不同。

对于String来说,它提供的操作应该是对于字符串本身的一些操作,而不是创建或改变一个字符串。

而对于StringBuilder来说,它种注重的就是建造的过程,比如果append(),insert()方法。

对比String与StringBuilder的提供的方法,可以明显的发现,String的方法只是提供字符串“应该”提供的服务,比如说求字串等等。

StringBuilder则更注重建造一个怎样的字符串,StirngBuilder提供的方法基本都是在改变字符串本身,例如:往某个位置插入某些字符、字符串的逆置等等。

使String的实例成为一个不可变的对象。
如果String提供了例如StringBuilder中的append()方法,String对象就是成为了可变对象。(在学习JavaSE的时候,就知道String的实例是不可变的,本质原因是String没有提供方法,而是将这些方法抽象出来一个StringBuilder)

String不是为了创建字符串。
听着好像有点矛盾,就像上面说的,使用构造方法有时候只是创建一个“外壳”。(我们通常使用的StringBuilder时,就是先new一个“空壳”,然后调用各种方法向这个“壳子”中填数据)

结构性模式

结构型模式描述如何将类或对象按某种布局组成更大的结构.它分为类结构模式和对象结构模式 ,欠着采用继承机制来组织接口和类,后者采用组合或聚合来组合对象

由于组合关系或聚合关系比击沉搞关系耦合度更低,满足合成复用原则,所以对象结构模式比类结构型具有更大的灵活性

结构型模式分为以下七种

  1. 代理(proxy)模式:为某对象提供一种代理以控制对象的访问.及客户端通过代理间接访问该对象,从而限制增强或修改该对象的一些特特性
  2. 适配器(adapter)模式:讲一个类的结构抓暖床客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类 能一起工作
  3. 桥接(Bridge)模式:将抽象与实现分离,使他们可以独立变化.它是用组合关系代替集成关系来实现的从而降低了抽象和实现这俩个可变维度的耦合度.
  4. 妆饰(Facade)模式:运用共享技术来有效的支持大量粒度对象的复用
  5. 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问
  6. 享元(Flyweight)模式:运用共享技术支持大量细粒度对象的复用
  7. 组合(Composite)模式:将对象组合成树状层次结构,使用户单个对象和组合对象具有一致的访问性

以上七种结构形模式,除了适配器模式分为类结构行模式和对象结构型模式俩种,其他的全部属于结构型模式

代理模式

定义:由于某些原因需要给对象提供一个代理以控制该对象的访问.这是对象不适合或者不能直接引用目标对象,代理对象作为访问和目标对象之间的中介

结构:

  1. 抽象主题类:通过接口或抽象类声明真实主题和代理对象的业务方法
  2. 真实主题类:实现了抽象主题类中的具体业务,时代里对象所代表的真实对象,是最终要引用的对象
  3. 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问控制或扩展真实主题的功能

优点:

  • 代理模式在客户端与目标类中间起到一个中介作用和保护目标对象的作用
  • 代理对象可以扩展目标对象的功能
  • 代理模式能讲客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了可扩展西

缺点

  • 代理模式会造成系统中的类数量增加
  • 在客户端和目标增加一个代理对象,会造成请求速度变慢
  • 增加了系统的复杂度

如何解决以上缺点呢?答案是可以使用动态代理(动态代理速度依然更慢)

静态:由程序员创建代理类或特定工具自动生成源码再对其编译,在程序运行钱.class文件就已经存在了

动态:在程序运行是,运用反射机制动态创建而成

适配器模式

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作.

适配器模式分为类结构型模式和对象结构型模式俩种,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中相关组件的内部结构,所以应用相对较少些

实现

比如读卡器是作为存储卡和笔记本之间的适配器

适配器模式可以采用多重继承的方式实现,入c++可以定义一个适配器同时集成当前系统的业务接口和现有组件库中以存在的组件接口.java不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时有几成现有组件库中已存在的组件

结构

  1. 目标结构:当前系统业务所期待的接口,它可以是抽象类的接口
  2. 适配者类:他是被访问和适配的显存组件库中的组件接口
  3. 适配器类:是一个转换器,通过几成或者引用适配对象,把适配对这接口转换成目标接口,让客户按接口访问格式访问适配者

类适配器模式的代码如下

package adapter;
//目标接口
interface Target
{
    public void request();
}
//适配者接口
class Adaptee
{
    public void specificRequest()
    {       
        System.out.println("适配者中的业务代码被调用!");
    }
}
//类适配器类
class ClassAdapter extends Adaptee implements Target
{
    public void request()
    {
        specificRequest();
    }
}
//客户端代码
public class ClassAdapterTest
{
    public static void main(String[] args)
    {
        System.out.println("类适配器模式测试:");
        Target target = new ClassAdapter();
        target.request();
    }
}

对象适配器模式的代码

package adapter;
//对象适配器类
class ObjectAdapter implements Target
{
    private Adaptee adaptee;
    public ObjectAdapter(Adaptee adaptee)
    {
        this.adaptee=adaptee;
    }
    public void request()
    {
        adaptee.specificRequest();
    }
}
//客户端代码
public class ObjectAdapterTest
{
    public static void main(String[] args)
    {
        System.out.println("对象适配器模式测试:");
        Adaptee adaptee = new Adaptee();
        Target target = new ObjectAdapter(adaptee);
        target.request();
    }
}

对象适配器模式中的目标接口和适配者类的代码同类适配器模式一样,只要修改适配类和客户端的代码即可

个人认为对前端暴露的api就是适配器模式.

桥接模式

在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。

桥接模式定义:将抽象与实现分离,使他们可以独立变化

塔使用组合关系代替继承替换关系,从而降低了抽象和现实这俩个可变唯独的耦合

桥接模式主要包含以下角色

  • 抽象化角色:定义抽象类,并包含一个对实现化对象的引用
  • 扩展抽象化角色:是抽象画角色的子类,实现父类中的业务方法,并ton孤噢组合关系调用实现变化角色中的业务方法
  • 实现化角色:定义实现话角色的接口,供扩展抽象化角色调用
  • 具体实现化,角色:给出实现角色接口的具体实现

优点

  • 与实现分离扩展能力强
  • 符合开闭远着呢
  • 符合合成复用原则
  • 实现细节对客户透明

如spi就是桥接模式.

装饰器模式

定义在不变现有对象结构的情况下,动态地给该对象增加一些指责(即增强其额外的模式),它属于对象结构型模式

实现

通常情况下扩展一个类的干嘛呢会使用继承方式来实现.但继承具有静态特征,耦合度高,随着扩展功能的增多,子类会很膨胀.如果使用组合关系来创建一个包装对象(即妆饰对象)来包括真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标

优点

  • 灵活补充,比继承灵活,在不改变原有对象的情况下,动态的给对象扩展一个功能,即插即用
  • 通过使用不用妆饰类即这些修饰类的排列组合,可以实现不同的效果
  • 遵守开闭远原则

缺点:装饰类会增加许多子类,过渡使用会增加程序得复杂性

外观模式

外观(Facade)模式又叫做门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式

在日常编码中,我们都在有意无意的大量使用外观模式.只要是高层模块需要调度多个子系统,我们丢回自觉地创建一个新的类封装这些子系统,提供精简的接口,让高层模块可以更加入一地调用这些子系统的功能.尤其是各种第三方SDK,开源库,很大概率都会使用外观模式

优点,是迪米特法则的典型应用

  • 降低了子系统与客户之间的耦合度,是的子系统的变化不会一个系调用它的客户类
  • 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易.
  • 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

缺点

  • 不能很好地限制客户使用子系统类,很容易带来为止风险
  • 增加新的子类系统可能需要修改外观类或客户端的源码,违反了开闭原则

网关就是一种门面模式

享元模式

在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。

定义运用共享技术来有效支持大量细粒度对象的复用.它通过共享已经存在的对象来大幅度减少以需要创建的对象数量,避免大量相似类的开销,从而提高系统的资源利用率

享元模式的主要优点是,相同对象只要保存一份,降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力

其主要缺点是

  • 为了使对象可以共享,需要将一些不能共享的状态外部化,浙江增加程序的复杂性
  • 读取享元模式的外部状态会使得运行时间稍微变长

实现

享元模式提出了俩个要求,细粒度和共享对象.因为要求细粒度,所以不可避免地,会使对象数量最多且性质相近,此时我们就将这些对象的信息分为俩部分:内部状态和外部状态

  • 内部状态指对象共享出来的信息,存储在享元信息内部,并且不会随环境的改变而改变
  • 外部状态指对象得以依赖一个标记,随环境的改变而改变,不可共享

比如连接池中的链接对象,保存在连接对象中的用户名密码,连接url信息等,在创建对象的时候就设置好了,不会随环境改变,这些为内部状态.而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态

享元模式的本职是共享对象,降低内存消耗

比如线程池.连接池,各种池化技术都是享元模式

组合模式

将对象组合成树形结构来表示整体/部分的关系,允许用户以相同的方式处理单独对象和组合对象

组合模式一般用来表述整体与部分的关系,它讲对象组织到树形结构中 ,顶层节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点

组合对象有用一个或者多个组件对象,因此组合对象的操作可以委托个组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象

优点:

  • 组合模式使得客户端代码可以一直地处理单个对象和组合对象,无需关心自己处理的是单个对象还是组合对象,简化了客户端代码
  • 更容易在组合体内加入新的对象,客户端不会因为假如了新的对象而更改源代码,满足开闭原则

缺点

  • 设计较复杂,客户端需要花更多时间理清类之间的层次关系
  • 不容易限制容器的构建
  • 不容易用集成的方法来来增加构建新功能

组合模式是一个树形的结构,有叶子节点,有树枝节点,树枝下面还可以有叶子。 而装饰者模式是一个俄罗斯套娃的结构,外层套内层,不断的进行装饰。区别还是蛮明显的。他们有一个共同点就是所有的类都继承自同一个抽象类(或者对象)。

行为型模式

行为模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎么样互相协作完成单个类和对象无法完成的任务,它涉及算法与对象间指责的分配.

行为模式分为类行为模式和对象行为模式,前者采用继承机制在类间分派行为,后者采用组合或者聚合在对象之间分配行为.由于组合关系或聚合关系比集成关系耦合度更低,满足合成复用原则,所以对象行为模式比类行为模式有更大的灵活性

行为模式包含以下11种

  1. 模板方法(Template Method),模式:定义一个操作中的算法骨架,将算法一些的步骤延迟到子类中,这样使得子类在快要不改变算法结构的情况下冲定义算法的某些特定步骤
  2. 策略(Srtategy)模式:定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的改变不会影响使用算法的客户
  3. 命令(Command)模式:讲一个请求封装为一个对象,使发出去的请求责任和执行请求的责任分隔开
  4. 指责链(Chain of Responsibility)模式把请求链中的一个对象传到下一个对象,直到请求被响应为止.通过这种方式解耦
  5. 状态(State)模式:允许一个对象在其内部发生改变是改变其行为能力
  6. 观察者(Observer)模式:多个对象存在一对多关系,当一个对象发生改变时,把这个改变通知给其他多个对象
  7. 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间交互关系,降低系统中对象之间的耦合度,使原有对象之间不必相互了解
  8. 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据而不暴露聚合对象的内部展示
  9. 访问者(Vistor)模式:在不改变集合元素的前提下,为集合中的每个元素提供多种访问方式,即每个元素都有多个访问者对象访问.
  10. 备忘录模式:再不破坏封装的前提下,获取保存一个对象的内部状态,以便后续恢复它
  11. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言橘子的解释方法,即解释器

以上11种行为模式,除了模板方法和解释器模式是类行为模式,其他的全部属于对象行为模式.

模板模式

定义一个操作中的算法骨架,而讲算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤.它是一种类行为模式

该模式的主要优点如下

  1. 封装了不变部分,拓展可变部分.它认为是不变部分的算法封装到父类中实现,而把可变算法由子类集成实现,便于子类继续扩展
  2. 它在父类中提取了公共的部分代码,便于代码复用
  3. 部分方法是由子类实现的,一次子类可以通过扩展方式增加响应功能,符合开闭原则

缺点

  1. 对于每个不同的实现都需要一个子类的定义,这会导致增加子类的个数,系统更加庞大,设计也更加抽象,增加了系统的复杂度
  2. 父类中的抽象方法由子类实现,子类执行的结果非影响父类的结果,这导致一种反向结构控制,提高了代码阅读难度
  3. 由于继承关系自身缺点,如果父类添加新的抽象方法,啧所有子类都要改一遍

模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。现在来介绍它们的基本结构。(父类调用子类)

AQS就是基于模板设计模式实现的

策略模式

定义了一些列算法,并将每个算法封装起来,使他们可以相互替换,切算法的变化不会影响使用算法的客户.

策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理

实现

策略模式是准备一组算法,并将这组算法封装到一些列的策略类里面,作为抽象策略类的子类.策略模式的中心不是如何实现算法,而是如何组织这些算法,让程序更灵活,具有更好的维护性和扩展性.

比如现在系统有三个用户,每个用户权益在系统中是不一致的,可以将每个用户,例如vip用户,普通用户,未登录用户抽象三个类,分别定义它的方法。之后只需要对类进行替换,不需要每次进行判断

命令模式

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分隔开.这样俩者之间通过命令对象进行沟通

,这样方便将命令对象进行存储,传递,调用,增加与管理

将命令封装成对象中,以便使用命令来参数化,其他对象,或者将命令对象放入队列中进行排队.或者将命令对象的操作记录到日志中,以及支持可撤销的操作

主要角色

  1. 抽象命令类:声明执行命令的接口,拥有执行命令的抽象方法
  2. 具体命令类抽象命令类的具体实现类,拥有接受者对象,并通过调用接受者的给你来完成命令要执行的操作
  3. 实现着/接受者:执行命令功能相关的操作,是具体命令对象业务的真正实现着
  4. 调用者/请求者:是请求的发送者,它通常拥有很多的命令对象,斌ton孤噢访问命令对象来执行相关请求,不直接访问接收者
package command;
import javax.swing.*;
public class CookingCommand {
    public static void main(String[] args) {
        Breakfast food1 = new ChangFen();
        Breakfast food2 = new HunTun();
        Breakfast food3 = new HeFen();
        Waiter fwy = new Waiter();
        fwy.setChangFen(food1);//设置肠粉菜单
        fwy.setHunTun(food2);  //设置河粉菜单
        fwy.setHeFen(food3);   //设置馄饨菜单
        fwy.chooseChangFen();  //选择肠粉
        fwy.chooseHeFen();     //选择河粉
        fwy.chooseHunTun();    //选择馄饨
    }
}
//调用者:服务员
class Waiter {
    private Breakfast changFen, hunTun, heFen;
    public void setChangFen(Breakfast f) {
        changFen = f;
    }
    public void setHunTun(Breakfast f) {
        hunTun = f;
    }
    public void setHeFen(Breakfast f) {
        heFen = f;
    }
    public void chooseChangFen() {
        changFen.cooking();
    }
    public void chooseHunTun() {
        hunTun.cooking();
    }
    public void chooseHeFen() {
        heFen.cooking();
    }
}
//抽象命令:早餐
interface Breakfast {
    public abstract void cooking();
}
//具体命令:肠粉
class ChangFen implements Breakfast {
    private ChangFenChef receiver;
    ChangFen() {
        receiver = new ChangFenChef();
    }
    public void cooking() {
        receiver.cooking();
    }
}
//具体命令:馄饨
class HunTun implements Breakfast {
    private HunTunChef receiver;
    HunTun() {
        receiver = new HunTunChef();
    }
    public void cooking() {
        receiver.cooking();
    }
}
//具体命令:河粉
class HeFen implements Breakfast {
    private HeFenChef receiver;
    HeFen() {
        receiver = new HeFenChef();
    }
    public void cooking() {
        receiver.cooking();
    }
}
//接收者:肠粉厨师
class ChangFenChef extends JFrame {
    private static final long serialVersionUID = 1L;
    JLabel l = new JLabel();
    ChangFenChef() {
        super("煮肠粉");
        l.setIcon(new ImageIcon("src/command/ChangFen.jpg"));
        this.add(l);
        this.setLocation(30, 30);
        this.pack();
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public void cooking() {
        this.setVisible(true);
    }
}
//接收者:馄饨厨师
class HunTunChef extends JFrame {
    private static final long serialVersionUID = 1L;
    JLabel l = new JLabel();
    HunTunChef() {
        super("煮馄饨");
        l.setIcon(new ImageIcon("src/command/HunTun.jpg"));
        this.add(l);
        this.setLocation(350, 50);
        this.pack();
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public void cooking() {
        this.setVisible(true);
    }
}
//接收者:河粉厨师
class HeFenChef extends JFrame {
    private static final long serialVersionUID = 1L;
    JLabel l = new JLabel();
    HeFenChef() {
        super("煮河粉");
        l.setIcon(new ImageIcon("src/command/HeFen.jpg"));
        this.add(l);
        this.setLocation(200, 280);
        this.pack();
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    public void cooking() {
        this.setVisible(true);
    }
}

比如CQRS

业务逻辑大部分都发生在写入的时候,例如用户购买商品提交订单时,我们要验证库存,用户信息订单数据是否有效等。如果从传统DDD的角度看,Command类似于Application Service,用户的命令(如提交订单)会以Command的形式得到执行,而Command中也不会带有业务逻辑,Command中做的事情基本上是:通过Repository得到相关的领域对象,调用某些领域服务(Domain Service)执行一些操作(业务逻辑都将保留在领域模型中),然后执行Commit或SaveChanges之类的方法提交改动,之后,相关的数据就会写入到Write DB中(图的DB,下文统称Write DB)。需要注意的是,UI上的查询都是查Read DB,而不是Write DB。

责任链模式

netty就是典型的责任链模式

为了避免请求发送者与多个请求矗立着耦合在一起,于是讲所有请求的矗立着通过前一对象记住其下一个对象的引用而连成一条责任链;有请求发生时,可讲请求沿着这条链传递,知道有对象处理它为止

责任链模式也叫指责链模式

在职责链模式中,客户只需要讲请求发送到职责链上即可,无需关心请求的细节和请求的传递过程,请求会自动进行传递.所以职责链讲请求的发送者和请求的处理者解耦了

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

角色

  1. 抽象处理者:定义一个处理请求的接口,包含抽象处理方法和后续连接
  2. 具体处理者角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理则处理,否则将请求传给后继者
  3. 客户:床架能处理链,并向链头的具体矗立着对象提交请求,它不关心处理传递的过程

其中我们可以看到,在springMVC中,DispatcherServlet这个核心类中使用到了HandlerExecutionChain这个类,他就是责任链模式实行的具体类。在DispatcherServlet的doDispatch这个方法中,我们可以看到它贯穿了整个请求dispatch的流程:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            ModelAndView mv = null;
            Exception dispatchException = null;

            try {
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                // 获取该请求的handler,每个handler实为HandlerExecutionChain,它为一个处理链,负责处理整个请求
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null || mappedHandler.getHandler() == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 责任链执行预处理方法,实则是将请求交给注册的请求拦截器执行
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 实际的执行逻辑的部分,也就是你加了@RequestMapping注解的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
                // 责任链执行后处理方法,实则是将请求交给注册的请求拦截器执行
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 处理返回的结果,触发责任链上注册的拦截器的AfterCompletion方法,其中也用到了HandlerExecutionChain注册的handler来处理错误结果
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            // 触发责任链上注册的拦截器的AfterCompletion方法
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }

从上面的代码中我们可以看到,HandlerExecutionChain主要负责请求的拦截器的执行和请求的处理,但是他本身不处理请求,只是将请求分配给在链上注册的处理器执行,这是一种责任链的实现方式,减少了责任链本身与处理逻辑之间的耦合的同时,规范了整个处理请求的流程

具体流程

状态模式

在软件开发过程中,应用程序中的部分对象可能会根据不同的情况作出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态.当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变.

对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。

以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。

定义对有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变时的行为

实现

状态模式把受环境改变的对象行为宝在不同的状态对象里,其图是让一个对象在其内部状态改变的时候,其他行为随之改变.

结构和角色

  • 环境类角色:也叫上下文,定义了客户端需要的接口,内部维护一个当前的状态,并负责具体的切换
  • 抽象状态:定义一个接口用封装环境对象中的特定状态锁对应的行为,可以有一个或多个行为
  • 具体状态:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换

优点

  1. 结构清晰,状态模式讲与特点状态相关的行为局部化到一个状态中,并且讲不通状态的行为分割开来,满足单一指责
  2. 讲状态转换显示话,减少对象之间的相互依赖.讲不通的状态引入独立的对象中会使得状态转换变得更加明确,而且减少对象之间的相互依赖
  3. 状态指责明确有利于程序的扩展.通过定义新的子类,很容易地增加状态的转化

缺点

  1. 状态模式的使用会增加系统类的个数.
  2. 状态模式的结构与实现都较为复杂,如果使用不当会当值程序结构和代码的混乱
  3. 状态模式对开闭原则支持不太好

比如現在订单有各种状态,但是需要确定订单状态是否是正常的.我们可以给订单的状态抽象一个状态类.状态类中直接提供各个属性,比如当前订单状态是否正常.以及当前订单能进行的操作(用于接口检查)

观察者模式

一个对象存在一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

这种模式有的时候又称作发布订阅模式,

实现

实现观察者模式是要注意目标对象和具体观察者之间不能直接调用,否则将使俩者之间紧密耦合起来,这违反了面向对象设计原则

角色:

  1. 抽象主题:也叫抽象目标类,它提供了一个用于保存观察者对象和狙击类和增加,删除观察者对象的方法,以及通知所3有观察者对象的抽象方法
  2. 具体主题:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部发生改变时,通知所有注册过的观察者对象
  3. 抽象观察者:是一个抽象类或者接口,它包含了要一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
  4. 具体观察者:实现抽象观察者中的定义抽象方法,以便在得到目标的更改时更新自身的状态

比如spring中的event就是观察者模式,当一个行为触发这个event的时候,全局都能监听到.

中介者模式

定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且独立地改变它们之间的交互

终结者模式又叫调停模式,他是迪米特法则的典型应用

在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者

优点

  1. 类之间各司其职,符合迪米特法则
  2. 降低了对象之间的耦合性,使得对象易于被独立地被复用
  3. 将对话之间的一对多关系转换为一对一的关联提高系统灵活性,使得系统更易于扩展

缺点:中介者模式讲原本多个对象直接的相互依赖变成了中介者和多个同时类的依赖关系.当同事类越多时,中介者就会越臃肿,变得复杂且难以维护\

迭代器模式

提供一个对象来顺序访问聚合对象内的一系列数据,而不暴露聚合对象的内部表示.

迭代器模式是一种对象行为模式

优点:

  1. 访问一个聚合对象的内容无需暴露它的内部展示
  2. 遍历任务交由迭代器完成,简化了聚合类
  3. 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历
  4. 增加新的聚合类和迭代器都很方便无需修改原有代码
  5. 封装良好,为遍历不同的聚合数据提供一个统一接口

在开发中一般不会自己写迭代器.除非需要定制一个自己实现的数据结构对应的迭代器,否则开源框架提供的API完全够用

备忘录模式

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以备你以后当需要时能恢复到原先保存的状态

又叫快照模式

实现,角色

  • 发起人:记录当前时刻的内部状态信息,提供创建备忘录和回复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
  • 备忘录:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人
  • 管理者:对备忘录进行管理,提供保存于获取备忘录功能,但不能对备忘录的内容进行访问与修改

用clone方法可以很好的实现这些,序列化和反序列化也是.

解释器模式

给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。

文法:文法是用于描述语言的语法结构形式规则.

句子:是语言的基本单位,是语言集中的一个元素,由终结符构成,能由文法推导出

语法树:是句子结构的一种树形表示,它代表了句子的推倒结果,有利于理解句子语法结构的层次

比如正则表达式,理论上任何解释形语言都算?

访问者模式

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

从定义可以看出结构对象是使用访问者模式必备条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于Java语言当中的collection概念了。

比如collection可以存在不同的集合实现,如arraylist,linkedlist。其中传入的泛型也可以是任意类.

Last modification:October 25, 2023
如果觉得我的文章对你有用,请随意赞赏