跳转至

设计模式总结

系统总结一下

创建型模式

单例模式

定义:这个顾名思义,就是全局只使用一个实例,一般使用在需要资源复用的情况,比如数据库连接,线程池等等。

实现:常见的构建方式,有懒汉式和饿汉式两种。

单例和全局静态变量的区别? * 如果简单使用且生命周期都是等同于整个程序,则没有区别 * 如果想更细粒度的维护实例的生命周期,更好的封装实例的使用方式,则使用单例模式

工厂模式

其注重的是剥离创建实例的逻辑,将创建实例的逻辑从业务逻辑中抽离出来,通过工厂类来创建实例。

定义:一种封装对象创建过程的方式,使得客户端代码无需关心具体对象的创建逻辑,只需要通过统一的接口来获取对象。核心就是将对象的创建逻辑和使用逻辑分离。其遵循开闭原则,增加新的产品类的时候,不需要修改使用逻辑的代码,只需要扩展工厂逻辑就可以。

常见类型:

  • 简单工厂:根据参数来来决定返回对应对象。
  • 标准工厂:先定义一个创建对象的接口,通过实现类来决定如何创建对象。使用逻辑中可以通过参数控制具体要得到哪个工厂的实现类,然后通过工厂的实现类来创建对象。
  • 抽象工厂:维护一个或多个工厂,每个工厂负责创建一个产品系列。

建造者模式

其注重的是创建一个复杂对象,将多个部件的创建过程和装配过程分开,将复杂对象的创建和对象的使用分离。

定义:它用于将复杂对象的构建过程与其表示分离,使得同样的构建逻辑可以创建不同的表示。

核心组成:

  1. Product,也就是内部要创建的对象。
  2. Builder,负责创建Product的各个部分,并定义返回Product的接口。
  3. Director,负责调用Builder的接口,完成产品的创建。
  4. Client,负责创建Builder,并调用Director的接口,完成产品的创建。

也就是说,使用方式就是Client调用Director内部封装的Builder使用逻辑,从而完成内部对象的构建过程,最终返回的是一个构建完整的对象实例。

结构型模式

门面模式

经典例子就是日志系统

定义:它为复杂的子系统提供一个统一的高层接口,使得子系统的使用变得更加简单。

核心组成:

  1. Client类,通过接口调用Facade类的客户端代码,无需了解子系统的实现细节
  2. Facade类,为子系统提供接口,并定义子系统的访问方式
  3. SubSystem类,子系统接口的具体实现类

扩展:如果子系统接口实现类需要动态加载,则可以和SPI机制/直接反射结合,后者将符合要求的,实现Facade类接口的实现类加载到JVM中,然后供Client通过接口来调用。 也就是说,当有动态扩展需求的时候,门面模式通常和SPI/反射使用。

装饰器模式

定义:允许向一个现有的对象添加新的功能,同时又不改变其结构。

核心组成:

  1. Component:抽象组件类
  2. ConcreteComponent:具体组件类,这个就是将要被添加功能的对象
  3. Decorator:装饰器基类。必须持有一个Component对象 & 必须实现Component接口
  4. ConcreteDecorator:具体装饰器类。 继承Decorator类 & 覆盖Component接口方法
  5. Client:客户端,想要给组件增强功能的时候,只需要一行代码,将组件包一层修饰器即可,由于修饰器继承了抽象组件类,所以Client调用此时的修饰实例,内部就执行的是被增强(修饰)的组件的接口方法。

适配器模式

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

核心组成:

  1. Target:目标接口,定义客户所期待的接口。
  2. Adaptee:被适配者,定义被适配的接口。
  3. Adapter:适配器,将Adaptee接口转换成Target接口。
  4. Client:客户,使用Target接口,无需关注实际调用的是适配器还是原始实现。

核心思想:就是通过中间层将不兼容的接口进行兼容。

代理模式

定义:代理模式,也叫委托模式,为一个对象提供一个替身(代理),以控制对这个对象的访问。

核心组成:

  1. Subject:抽象功能类,定义接口
  2. RealSubject:具体功能类,实现接口。
  3. Proxy:代理类,持有被代理的功能实例。
  4. Client:客户端应用代码,通过代理类调用被代理的功能实例。

常见场景

  1. 远程代理,比如RPC中的远程对象
  2. 虚拟代理,实现lazy效果

享元模式

定义:是一种结构性设计模式,它的核心目的是通过共享技术来减少创建对象的数量,从而降低内存使用和提升性能,特别适用于大量细粒度对象的场景。

核心组成:享元模式将对象的状态分为内部状态和外部状态。内部状态可以共享状态,独立于具体的场景。而外部状态依赖具体场景,不可以共享,需要通过客户端传入。

  • Flyweight: 抽象类或者接口,定义了对象的行为
  • ConcreteFlyweight: 具体享元类,继承抽象享元类,实现抽象享元类定义的接口
  • FlyweightFactory: 享元工厂类,创建享元对象,并管理对象,确保对象唯一
  • Client: 使用享元对象的客户端

适用场景:单个对象创建成本高,且可复用性强,于是将内部状态和外部状态分离开,比如起多个POD(一组worker),然后每个task进来,都带有自己的外部状态,给POD注入自己的状态,同时复用镜像的状态为自己工作。

行为类模式

策略模式

定义:它允许你定义一系列算法或行为,并将它们封装在独立的类中,使得它们可以互换使用,从而让对象在运行时能够动态地改变其行为。

核心组成:

  1. Strategy:策略接口,定义了算法的抽象方法。
  2. ConcreteStrategy:具体策略类,实现了策略接口,定义了具体的算法。
  3. Context:上下文类,持有策略对象,通过策略对象执行算法。通常这里就可以使用set方法来动态设置策略对象,之后再调用execute方法来执行算法。

和门面模式的区别? 都是接口的使用方式,但是策略模式注重行为的切换,而门面模式则更注重对结构的封装。

SPI/反射等动态调用,只要是接口实现类需要被加载,都可以被使用,并不关注到底是门面模式,还是策略模式。

模版模式

定义:定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。

核心组成:

  1. 抽象类:定义抽象方法,定义算法的骨架,将一些步骤的实现延迟到子类中。
  2. 具体类:实现抽象方法,提供算法的实现。

这个是最简单,最常用的行为类模式了

迭代器模式

定义:迭代器模式允许对象控制对集合对象中元素的访问,并控制访问的顺序。

核心组成:

  1. 迭代器接口,定义如何遍历和访问集合中的元素。
  2. 集合接口,定义集合对象如何创建迭代器。
  3. 具体实现类,首先在内部通常定义具体迭代器类,实现迭代器接口,并实现具体的遍历和访问逻辑。 然后定义如何创建迭代器,返回具体迭代器类的实例。

责任链模式

这个在Netty中见到过,大数计算引擎中的底层RPC框架常常使用Netty来实现。

定义:将请求的发送者和接收者解耦,并让多个对象都有机会处理这个请求。这些对象形成一条链,请求沿着这条链传递,直到被处理或终止。

核心组成:

  1. Handler:处理者,定义了处理请求的接口。
  2. ConcreteHandler:具体的处理者,继承Handler,实现处理请求的接口。这里可以自己处理,也可以传递给下一个Handler处理。
  3. Client:客户端。首先创建请求,然后创建Handler Chain,最后将请求发送到第一个Handler即可。

常见场景:就是创建一个处理链,处理请求。中间做多级过滤,拦截等。

访问者模式

这个只在语法解析部分看到过,用于遍历和访问AST树节点。比如SparkSQL的g4文件生成的语法树的遍历。

定义:在不修改已有类结构的前提下,为对象结构中的元素增加新的操作逻辑。

核心组成:

  1. Visitor 抽象访问者,定义对每一种节点的访问方法
  2. ConcreteVisitor 具体访问者,实现对每种节点的访问方法
  3. Element 抽象元素,定义一个接口,用于接受访问者访问
  4. ConcreteElement 具体元素,实现抽象元素中的接口,提供访问者访问的方法
  5. ObjectStructure 抽象对象结构,定义一个接口,用于管理元素,并定义一个接口,用于接受访问者开始访问
  6. Client,首先创建对象结构,通常为树形。然后创建不同功能的访问者,分别访问对象树,产生不同的访问效果,收集到不同的信息。举一个简单的例子:比如一个有三角形和圆形的树,访问者有求面积和求周长的访问者。则计算面积的访问者,访问到三角形的时候(被该节点使用accept方法接收进去,允许其使用visit方法收集当前节点信息),会使用三角形的面积公式,访问圆形的时候,会使用圆形的面积公式。