设计模式总结¶
系统总结一下
创建型模式¶
单例模式¶
定义:这个顾名思义,就是全局只使用一个实例,一般使用在需要资源复用的情况,比如数据库连接,线程池等等。
实现:常见的构建方式,有懒汉式和饿汉式两种。
单例和全局静态变量的区别? * 如果简单使用且生命周期都是等同于整个程序,则没有区别 * 如果想更细粒度的维护实例的生命周期,更好的封装实例的使用方式,则使用单例模式
工厂模式¶
其注重的是剥离创建实例的逻辑,将创建实例的逻辑从业务逻辑中抽离出来,通过工厂类来创建实例。
定义:一种封装对象创建过程的方式,使得客户端代码无需关心具体对象的创建逻辑,只需要通过统一的接口来获取对象。核心就是将对象的创建逻辑和使用逻辑分离。其遵循开闭原则,增加新的产品类的时候,不需要修改使用逻辑的代码,只需要扩展工厂逻辑就可以。
常见类型:
- 简单工厂:根据参数来来决定返回对应对象。
- 标准工厂:先定义一个创建对象的接口,通过实现类来决定如何创建对象。使用逻辑中可以通过参数控制具体要得到哪个工厂的实现类,然后通过工厂的实现类来创建对象。
- 抽象工厂:维护一个或多个工厂,每个工厂负责创建一个产品系列。
建造者模式¶
其注重的是创建一个复杂对象,将多个部件的创建过程和装配过程分开,将复杂对象的创建和对象的使用分离。
定义:它用于将复杂对象的构建过程与其表示分离,使得同样的构建逻辑可以创建不同的表示。
核心组成:
- Product,也就是内部要创建的对象。
- Builder,负责创建Product的各个部分,并定义返回Product的接口。
- Director,负责调用Builder的接口,完成产品的创建。
- Client,负责创建Builder,并调用Director的接口,完成产品的创建。
也就是说,使用方式就是Client调用Director内部封装的Builder使用逻辑,从而完成内部对象的构建过程,最终返回的是一个构建完整的对象实例。
结构型模式¶
门面模式¶
经典例子就是日志系统
定义:它为复杂的子系统提供一个统一的高层接口,使得子系统的使用变得更加简单。
核心组成:
- Client类,通过接口调用Facade类的客户端代码,无需了解子系统的实现细节
- Facade类,为子系统提供接口,并定义子系统的访问方式
- SubSystem类,子系统接口的具体实现类
扩展:如果子系统接口实现类需要动态加载,则可以和SPI机制/直接反射结合,后者将符合要求的,实现Facade类接口的实现类加载到JVM中,然后供Client通过接口来调用。 也就是说,当有动态扩展需求的时候,门面模式通常和SPI/反射使用。
装饰器模式¶
定义:允许向一个现有的对象添加新的功能,同时又不改变其结构。
核心组成:
- Component:抽象组件类
- ConcreteComponent:具体组件类,这个就是将要被添加功能的对象
- Decorator:装饰器基类。必须持有一个Component对象 & 必须实现Component接口
- ConcreteDecorator:具体装饰器类。 继承Decorator类 & 覆盖Component接口方法
- Client:客户端,想要给组件增强功能的时候,只需要一行代码,将组件包一层修饰器即可,由于修饰器继承了抽象组件类,所以Client调用此时的修饰实例,内部就执行的是被增强(修饰)的组件的接口方法。
适配器模式¶
定义:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
核心组成:
- Target:目标接口,定义客户所期待的接口。
- Adaptee:被适配者,定义被适配的接口。
- Adapter:适配器,将Adaptee接口转换成Target接口。
- Client:客户,使用Target接口,无需关注实际调用的是适配器还是原始实现。
核心思想:就是通过中间层将不兼容的接口进行兼容。
代理模式¶
定义:代理模式,也叫委托模式,为一个对象提供一个替身(代理),以控制对这个对象的访问。
核心组成:
- Subject:抽象功能类,定义接口
- RealSubject:具体功能类,实现接口。
- Proxy:代理类,持有被代理的功能实例。
- Client:客户端应用代码,通过代理类调用被代理的功能实例。
常见场景
- 远程代理,比如RPC中的远程对象
- 虚拟代理,实现lazy效果
享元模式¶
定义:是一种结构性设计模式,它的核心目的是通过共享技术来减少创建对象的数量,从而降低内存使用和提升性能,特别适用于大量细粒度对象的场景。
核心组成:享元模式将对象的状态分为内部状态和外部状态。内部状态可以共享状态,独立于具体的场景。而外部状态依赖具体场景,不可以共享,需要通过客户端传入。
- Flyweight: 抽象类或者接口,定义了对象的行为
- ConcreteFlyweight: 具体享元类,继承抽象享元类,实现抽象享元类定义的接口
- FlyweightFactory: 享元工厂类,创建享元对象,并管理对象,确保对象唯一
- Client: 使用享元对象的客户端
适用场景:单个对象创建成本高,且可复用性强,于是将内部状态和外部状态分离开,比如起多个POD(一组worker),然后每个task进来,都带有自己的外部状态,给POD注入自己的状态,同时复用镜像的状态为自己工作。
行为类模式¶
策略模式¶
定义:它允许你定义一系列算法或行为,并将它们封装在独立的类中,使得它们可以互换使用,从而让对象在运行时能够动态地改变其行为。
核心组成:
- Strategy:策略接口,定义了算法的抽象方法。
- ConcreteStrategy:具体策略类,实现了策略接口,定义了具体的算法。
- Context:上下文类,持有策略对象,通过策略对象执行算法。通常这里就可以使用set方法来动态设置策略对象,之后再调用execute方法来执行算法。
和门面模式的区别? 都是接口的使用方式,但是策略模式注重行为的切换,而门面模式则更注重对结构的封装。
SPI/反射等动态调用,只要是接口实现类需要被加载,都可以被使用,并不关注到底是门面模式,还是策略模式。
模版模式¶
定义:定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。
核心组成:
- 抽象类:定义抽象方法,定义算法的骨架,将一些步骤的实现延迟到子类中。
- 具体类:实现抽象方法,提供算法的实现。
这个是最简单,最常用的行为类模式了
迭代器模式¶
定义:迭代器模式允许对象控制对集合对象中元素的访问,并控制访问的顺序。
核心组成:
- 迭代器接口,定义如何遍历和访问集合中的元素。
- 集合接口,定义集合对象如何创建迭代器。
- 具体实现类,首先在内部通常定义具体迭代器类,实现迭代器接口,并实现具体的遍历和访问逻辑。 然后定义如何创建迭代器,返回具体迭代器类的实例。
责任链模式¶
这个在Netty中见到过,大数计算引擎中的底层RPC框架常常使用Netty来实现。
定义:将请求的发送者和接收者解耦,并让多个对象都有机会处理这个请求。这些对象形成一条链,请求沿着这条链传递,直到被处理或终止。
核心组成:
- Handler:处理者,定义了处理请求的接口。
- ConcreteHandler:具体的处理者,继承Handler,实现处理请求的接口。这里可以自己处理,也可以传递给下一个Handler处理。
- Client:客户端。首先创建请求,然后创建Handler Chain,最后将请求发送到第一个Handler即可。
常见场景:就是创建一个处理链,处理请求。中间做多级过滤,拦截等。
访问者模式¶
这个只在语法解析部分看到过,用于遍历和访问AST树节点。比如SparkSQL的g4文件生成的语法树的遍历。
定义:在不修改已有类结构的前提下,为对象结构中的元素增加新的操作逻辑。
核心组成:
- Visitor 抽象访问者,定义对每一种节点的访问方法
- ConcreteVisitor 具体访问者,实现对每种节点的访问方法
- Element 抽象元素,定义一个接口,用于接受访问者访问
- ConcreteElement 具体元素,实现抽象元素中的接口,提供访问者访问的方法
- ObjectStructure 抽象对象结构,定义一个接口,用于管理元素,并定义一个接口,用于接受访问者开始访问
- Client,首先创建对象结构,通常为树形。然后创建不同功能的访问者,分别访问对象树,产生不同的访问效果,收集到不同的信息。举一个简单的例子:比如一个有三角形和圆形的树,访问者有求面积和求周长的访问者。则计算面积的访问者,访问到三角形的时候(被该节点使用accept方法接收进去,允许其使用visit方法收集当前节点信息),会使用三角形的面积公式,访问圆形的时候,会使用圆形的面积公式。