设计模式
针对某类问题的解决方案,复用成功的设计
模式名称 + 问题 + 解决方案 + 效果
创建型设计模式
目标:解决对象的创建问题,让对象的生成更灵活、解耦。
简单工厂模式
工厂类有一个创建方法,能够根据不同参数返回不同的对象实例。
存在的问题:如果需要增加新的功能,需要修改工厂类的内部代码,违反了开闭原则【软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的】。
工厂方法模式【类】
抽象工厂(AbstractFactory)
├── 创建产品(createProduct())抽象方法
└── 返回抽象产品(Product)类型
└── 具体工厂1(ConcreteFactory1)
└── 覆盖createProduct(),返回具体产品1(ConcreteProduct1)
└── 具体工厂2(ConcreteFactory2)
└── 覆盖createProduct(),返回具体产品2(ConcreteProduct2)
抽象产品(Product)
├── 定义产品接口或抽象类
└── 具体产品1(ConcreteProduct1)
└── 具体产品2(ConcreteProduct2)
客户端(Client)
├── 使用抽象工厂接口
└── 通过工厂方法获取具体产品关键点:
- 抽象工厂(AbstractFactory):定义了一个工厂方法(
createProduct()),但不具体实现它,由子类实现。 - 具体工厂(ConcreteFactory):继承抽象工厂,并覆盖工厂方法,返回对应的具体产品。
- 抽象产品(Product):定义产品需要实现的接口或抽象类。
- 具体产品(ConcreteProduct):实现抽象产品接口的具体类。
工厂方法模式 每个工厂只负责单一产品类的创建,工厂的数量翻倍但是进行扩展时只需要增加一个产品类,一个工厂类即可
- 定义一个用于创建对象(工厂)的接口,让子类决定实例化哪个类。使一个类的实例化延迟到子类。
interface Product {
public void info ();
}
class ProductA implements Product {
@Override
public void info () {
System.out.println("product A");
}
}
class ProductB implements Product {
@Override
public void info () {
System.out.println("product B");
}
}
interface Factory {
public Product createProduct ();
}
class FactoryA implements Factory {
@Override
public Product createProduct () {
return new ProductA();
}
}
class FactoryB implements Factory {
@Override
public Product createProduct () {
return new ProductB();
}
}抽象工厂模式
抽象工厂(AbstractFactory)
├── 创建产品族1(createProductA())抽象方法
├── 创建产品族2(createProductB())抽象方法
└── 具体工厂1(ConcreteFactory1)
├── 实现createProductA() → 返回具体产品A1
└── 实现createProductB() → 返回具体产品B1
└── 具体工厂2(ConcreteFactory2)
├── 实现createProductA() → 返回具体产品A2
└── 实现createProductB() → 返回具体产品B2
抽象产品A(ProductA)
└── 具体产品A1(ConcreteProductA1)
└── 具体产品A2(ConcreteProductA2)
抽象产品B(ProductB)
└── 具体产品B1(ConcreteProductB1)
└── 具体产品B2(ConcreteProductB2)
客户端(Client)
├── 使用抽象工厂接口
└── 通过工厂方法获取多个相关产品关键点:
- 抽象工厂(AbstractFactory):定义了一组工厂方法,每个方法对应一个产品族的创建。
- 具体工厂(ConcreteFactory):继承抽象工厂,并覆盖所有工厂方法,返回对应的具体产品。
- 抽象产品(ProductA、ProductB):定义不同产品族的公共接口或抽象类。
- 具体产品(ConcreteProduct):实现抽象产品接口的具体类,属于不同工厂对应的产品族。
在工厂方法模式基础上进行归类一个工厂不只是生成一个产品类,但是相同工厂生产的产品类具有相同的特征或者偏好 1.例如现在要两个类,数据库执行、数据库查询。两个类型oracle、mysql都实现这两个步骤,一共4个类。 2.声明2个工厂,oracle、mysql工厂。 3.每个工厂具有自己类型的执行、查询方法,即实例化1步骤中的类
生成器模式(Builder)
抽象建造者(Builder)
├── 创建部件方法(buildPart1()、buildPart2()等)抽象方法
├── 设置参数方法(setParam()等)抽象方法
└── 具体建造者1(ConcreteBuilder1)
├── 实现所有构建方法
└── 返回最终产品(getResult())
└── 具体建造者2(ConcreteBuilder2)
├── 实现所有构建方法
└── 返回最终产品(getResult())
产品(Product)
├── 包含多个部件或属性的复杂对象
导演类(Director)
├── 接收抽象建造者对象
└── 定义构建流程(通过调用建造者的方法)
客户端(Client)
├── 使用导演类或直接调用建造者
└── 获取最终产品关键点:
- 抽象建造者(Builder):定义所有构建方法的接口或抽象类,确保具体建造者有统一的构建步骤。
- 具体建造者(ConcreteBuilder):实现抽象建造者的方法,逐步构建产品(调用产品的方法构建不同的部件,建造者实例化时创建产品实例,内部储存了产品实例),并提供获取最终产品的接口。
- 产品(Product):最终被构建的复杂对象,包含多个部件或配置。
- 导演类(Director):可选,负责协调建造者按照固定流程构建产品(例如先构建A部件再构建B部件)。
生成器Builder定义每个组件如何生成,并能返回需要构建的对象 导演Director,通过在方法中传入生成器,调用生成器的组件生成方法,完成后并获得构建的对象
- 意图:将复杂对象的构件与它的表示分离,使得同样的构件过程可以创建不同的表示。
原型模式
抽象原型(Prototype)
├── clone() 抽象方法(或接口)
└── 具体原型1(ConcretePrototype1)
└── 实现clone(),返回自身类型的实例
└── 具体原型2(ConcretePrototype2)
└── 实现clone(),返回自身类型的实例
客户端(Client)
├── 持有原型对象引用
└── 通过clone()方法创建新对象关键点:
- 抽象原型(Prototype):定义克隆操作的接口或抽象方法。
- 具体原型(ConcretePrototype):实现克隆方法,提供具体的复制逻辑。
- 客户端(Client):直接操作原型对象,通过克隆生成新实例,无需关心创建细节。
原型模式,另一种实例化的方法,通过克隆已经实例化的对象完成。适用于构造函数耗时,避免重复执行构造函数;适用于对实例化后对象能够进行属性更改,需构造相同的对象。
Prototype声明复制自身的接口
ConcretePrototype实现复制自身操作
Client调用复制
- 用原型实例指定创建对象的种类,并通过复制这些原型创建新的对象
单例模式
单例类(Singleton)
├── 私有构造方法(private Singleton())
├── 静态私有实例(private static Singleton instance)
└── 静态公共获取方法(public static Singleton getInstance())
客户端(Client)
├── 通过`getInstance()`方法获取实例
└── 无法直接通过构造函数创建实例关键点:
- 私有构造方法:防止外部通过
new关键字直接创建实例。 - 静态私有实例:通过类自身创建唯一实例。
- 静态公共方法:
getInstance()用于返回唯一实例,确保客户端只能通过此方法访问。 - 饿汉式提前实例化,懒汉式调用时再实例化(线程不安全)。
- 保证一个类仅有一个实例,并提供一个访问它的全局访问点
- 构造方法声明为private,保证外部无法进行构建
结构型设计模式
目标:解决类或对象的组合问题,通过结构组合让不同功能模块协作。
适配器模式【类】
目标接口(Target)
├── 客户端期望的方法(method1()、method2()等)
适配器类(Adapter)
├── 实现目标接口(Target)
├── 持有适配者(Adaptee)的引用(组合方式)
└── 将目标方法调用适配到适配者的方法
适配者类(Adaptee)
└── 已有的接口或类,方法与目标接口不兼容
客户端(Client)
├── 通过目标接口操作适配器或适配者
└── 无需关心适配逻辑关键点:
- 目标接口(Target):客户端期望的接口,适配器必须实现它。
- 适配者类(Adaptee):已有的类,其接口需要被适配,但客户端无法直接使用。
- 适配器类(Adapter):
- 类适配器:通过继承适配者和实现目标接口(Java中不常用,因Java不支持多继承)。
class xx extends Adaptee implements Target - 对象适配器:通过组合适配者(更常用),将适配者的方法适配成目标接口的方法。
- 类适配器:通过继承适配者和实现目标接口(Java中不常用,因Java不支持多继承)。
- 客户端(Client):直接与目标接口交互,无需关心适配逻辑。
新增一个适配目标类(adapter) 实现适配目标类,接收适配前实例(adaptee),声明方法,内部调用适配前实例(adaptee)的方法。
- 将一个类的接口转换成客户希望的另外一个接口。
桥接模式
抽象类(Abstraction)
├── 持有实现类接口(Implementor)的引用
├── 依赖于实现类接口的方法(如operation())
└── 细化抽象类(RefinedAbstraction)
└── 继承抽象类,扩展功能
实现类接口(Implementor)
└── 定义实现类的共用方法(如implementOperation())
具体实现类(ConcreteImplementor)
└── 实现Implementor接口的方法
客户端(Client)
├── 创建抽象类和具体实现类的组合实例
└── 通过抽象类接口调用实现类的方法关键点:
- 抽象类(Abstraction):定义高层接口,持有实现类接口的引用,通过组合关联实现。
- 细化抽象类(RefinedAbstraction):继承抽象类,扩展抽象功能。
- 实现类接口(Implementor):定义实现部分的共用方法,与抽象类接口解耦。
- 具体实现类(ConcreteImplementor):实现Implementor接口的具体方法。
- 组合关系:抽象类通过组合(
has-a)关系持有关联的实现类,而非继承(is-a)关系。 - 抽象类在构造函数中传入实现类的实例,抽象类的子类可调用实现类的方法。否则需要多重继承才能够调用两个类的方法。
需要根据情况进行多重继承的情况,每一重都有多个选择。 桥接模式通过将原本需要继承的类的实例作为参数传入构造函数,调用实例的方法即可。 将复杂系统中的稳定部分与易变部分解耦。通过组合优于继承的原则,构建弹性架构
- 将抽象与其实现部分分离,使他们都可以独立地变化
组合模式
抽象组件(Component)
├── 定义公共接口(如`operation()`、`add()`、`remove()`、`getChild()`)
叶子节点(Leaf)
├── 继承抽象组件
└── 实现具体操作(无子节点)
容器节点(Composite)
├── 继承抽象组件
├── 持有子组件的集合(组合关系) private属性
└── 实现容器特有的操作(如增删子节点)
客户端(Client)
├── 通过组件接口操作叶子或容器节点
└── 统一处理单个和组合对象关键点:
抽象组件(Component):定义所有叶子和容器节点的公共接口。
叶子节点(Leaf):代表不可再分解的单个对象(如文件、部门成员)。
容器节点(Composite):可以包含其他组件(叶子或容器),形成树形结构(如文件夹、组织部门)。
组合关系:容器节点通过集合(如
List<Component>)持有子组件。递归操作:容器节点的操作会递归调用子组件的方法(如遍历所有子节点)。
java@Override public void operation() { System.out.println("容器节点操作"); for (Component child : children) { child.operation(); // 递归调用子节点 } }
组合模式用于处理树形结构问题。
叶子节点类和非叶子节点类都从同一接口实现。
- 将对象组合成树形结构以表示“部分-整体”的层次结构,Composite使得用户对单个对象和组合对象的使用具有一致性。
装饰模式
抽象组件(Component)
├── 定义公共接口(如`operation()`)
具体组件(ConcreteComponent)
└── 实现Component接口的基础对象
装饰器抽象类(Decorator)
├── 继承Component接口
├── 持有Component对象的引用(组合关系)
└── 调用被装饰对象的方法
具体装饰器(ConcreteDecorator)
├── 继承Decorator
└── 在装饰器抽象类的基础上添加新功能(通过覆盖方法)
客户端(Client)
├── 通过Component接口操作被装饰或未被装饰的对象
└── 动态组合装饰器以扩展功能关键点:
- 抽象组件(Component):定义所有对象(包括装饰器)的公共接口。
- 具体组件(ConcreteComponent):基础对象,实现Component接口的核心功能。
- 装饰器抽象类(Decorator)(abstract extends Component):
- 组合关系:通过持有Component引用关联被装饰对象。
- 代理行为:默认调用被装饰对象的方法,具体装饰器在此基础上扩展。
- 具体装饰器(ConcreteDecorator):
- 职责扩展:在装饰器抽象类的基础上添加新功能(覆盖方法)。
- 递归装饰:可以装饰其他装饰器,形成链式扩展。
- 透明性:客户端无需区分具体组件和装饰器,直接通过Component接口调用。
装饰器模式:通过一个新类,传入已有的实例,在调用原对象方法前后添加新逻辑,从而达到扩展原有实例的目的
- 动态的给一个对象添加一些额外的指责。
外观模式
客户端(Client)
└── 使用外观类(Facade)提供的简单接口
外观类(Facade)
├── 依赖于多个子系统类(Subsystem Classes)
└── 封装子系统的复杂交互逻辑
子系统类(Subsystem Classes)
└── 实现具体功能的复杂模块(如电源、加热器、水泵等)关键点:
- 外观类(Facade):
- 提供一个简单的接口,隐藏子系统的复杂性。
- 调用子系统类的多个方法,协调它们的协作。
- 子系统类(Subsystem):
- 可能是一个或多个模块,各自独立完成复杂功能。
- 客户端通常不需要直接与子系统类交互。
- 依赖关系:外观类依赖子系统类,但子系统类之间相互独立或仅内部协作。
将某些子系统的操作封装到一个类中,客户端调用这个类的一个方法达到集成一系列操作的目的
- 为子系统的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一系统更加容易使用。
享元模式
抽象享元(Flyweight)
├── 定义操作接口(如`render()`)
├── 包含可共享的**内部状态**(如字体、颜色)
└── 提供方法接收**外部状态**(如位置、坐标)
具体享元(ConcreteFlyweight)
└── 继承抽象享元,实现共享逻辑,构造函数中传入内部状态
不共享享元(UnsharedConcreteFlyweight)
└── 可选:无法共享的具体享元(如临时对象)
享元工厂(FlyweightFactory)
├── 管理享元对象池(如`Map`)
└── 根据内部状态返回已存在的或新创建的享元对象
客户端(Client)
├── 通过工厂获取享元对象
└── 传递外部状态并调用享元方法关键点:
- 抽象享元(Flyweight):定义所有享元对象的公共接口,并声明内部状态。
- 具体享元(ConcreteFlyweight):实现抽象享元的接口,保存内部状态,依赖外部状态完成操作。
- 享元工厂(FlyweightFactory):
- 对象池:通过缓存已创建的享元对象,避免重复创建。
- 根据内部状态复用对象:如根据字体、颜色等键属性返回已有享元,不存在得享元则存入。
- 外部状态:不可共享的状态(如位置、坐标)由客户端或环境类传递,不保存在享元内部。
- 不共享享元:某些对象可能无法共享(如临时对象),但模式本身更关注可共享的对象。
分为外部状态和内部状态,外部为共享的数据
通过Map进行储存,返回已经创建的数据
- 运用共享技术有限地支持大量细粒度的对象
- 适用于
- 应用程序使用了大量对象,因此造成大量储存开销
- 对象的大多数状态可变为外部状态
代理模式
抽象主题(Subject)
├── 定义目标对象和代理的公共接口
真实主题(RealSubject)
└── 实现Subject接口,提供核心功能
代理(Proxy)
├── 实现Subject接口,持有RealSubject的引用
├── 控制对RealSubject的访问(如权限、缓存等)
└── 可选:增强或修改RealSubject的行为
客户端(Client)
└── 通过Subject接口操作代理或真实对象关键点:
- 抽象主题(Subject):
- 定义目标对象和代理的公共接口(可选,但推荐)。
- 如果省略,代理直接继承或组合真实对象。
- 真实主题(RealSubject):
- 实现核心业务逻辑的对象。
- 客户端通常不直接调用,而是通过代理间接访问。
- 代理(Proxy):
- 组合关系:持有RealSubject的引用。
- 控制访问:在调用RealSubject的方法前/后执行额外逻辑。
- 客户端(Client):
- 通过Subject接口与代理或真实对象交互,无需区分两者。
代理模式:通过一个新代理类,传入已有的被代理实例,重写原对象方法,从而达到代理目的。 和装饰者模式有些像,目的不同。装饰者模式为了增加功能;代理模式为了控制访问。 装饰者模式因为可能存在很多具体装饰者,所以抽离了一个abstract Class,而代理模式只是一对一的代理,其实具体实现很相像。但是单一装饰者也可以不抽离abstract class
- 为其他对象提供一种代理以控制对这个对象的访问
行为型设计模式
目标:解决对象之间的交互问题和职责分配问题,让系统更灵活、可扩展。
责任链模式
- 使多个对象都有机会处理请求,将对象连成一条链,沿着这条链传递请求,知道有一个对象处理它为止。
命令模式
抽象命令:能下发哪些命令 具体命令:实现抽象命令的具体方法,主体通过构造函数传入 调用者:声明新的方法,内部去调用具体命令的方法。具体命令通过构造函数传入
将调用者和执行者拆分,通过命令连接起来
- 将一个请求封装为一个对象,从而使得可以用不同的请求对客户进行参数化。
- 适合的情况
- 抽象出待执行的动作以参数化某对象
- 在不同的时刻指定,排列和执行请求
- 支持取消操作
- 支持修改日志
- 用构建在原语操作上的高层操作构建一个系统。
解释器模式【类】
- 定义一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
- 适用于当有一个语言需要解释执行,并可将该语言中的句子表示为一个抽象语法树时。
迭代器模式
interface Iterator { next() hasNext(); } 迭代器去实现这两个方法,保证对于客户端来说使用的时候能够通过next()按照预期进行数据的依次访问。 迭代器实现对某类数据结构进行按照预期的访问,不同的迭代器能够对相同的数据产生不同的访问方式。
- 提供一种顺序访问一个聚合对象中的各个元素
中介者模式
中介者模式 核心目标是解耦对象间的直接依赖关系。通过引入一个中介对象来统一管理多个对象间的交互,使得对象间不再直接通信,而是通过中介者进行协调。 中介者 发送消息 添加对象 对象 对应中介者 接收消息 中介者和对象之间互相有储存对应关系
发送流程: 发送消息时,对象定义方法 调用中介者的发送消息方法进行消息发送; 中介者处理消息(业务流程); 中介者通过遍历对象,通过对象的接收消息方法传递消息
- 用中介对象来封装一系列的对象交互,中介者使各个对象不需要显式地相互引用。
- 适用于
- 一组对象以定义良好但是复杂的方式进行通讯,产生的相互依赖关系结构混乱且难以理解。
- 一个对象引用其他很多对象并且与这些对象通信,从而使其耦合松散
- 定制一个分布在多个类中的行为,而又不想生成太多的子类
备忘录模式
Memento(备忘录)类的作用是 利用内部变量(private)去储存一次状态 Caretaker(管理者)的作用是 利用栈储存每次的状态 新增记录时,实例化Memento,添加到Caretaker中 回退时,在Caretaker中推出数据,并作为当前数据 Originator(发起人)创建Memento储存状态,恢复状态
- 在不破坏封装性的前提下捕获一个对象的内部状态,在对象之外保存这个状态,以后可以将对象恢复到原先保存的状态。
- 适用于
- 必须保存一个对象在某个时刻的状态,便于恢复到先前状态
- 如果一个接口来让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性
观察者模式
观察者 订阅 主题 主题 接口:添加/删除订阅者;向观察者发布消息 观察者 提供一个方法,用于主题发布消息时调 推模型是将消息发送到observer,而拉模型是将subjet传递给observer,由observer自己调用subject方法去拿数据
- 定义对象间的一对多依赖关系,当一个对象状态变化时,所有依赖于它的对象都得到通知并被自动更新
- 适用于
- 一个抽象模型有两方面,一个方面依赖于另一方面,将这两者封装在独立的对象中使他们可以各自独立地改变和复用。
- 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变时
- 一个对象必须通知其他对象,又不能假定其他对象是谁,即不希望这些对象是紧耦合的
状态模式
状态与行为的解耦
ins.doxx -> state.handle(this) -> ins.setState(state) and do something将状态统一封装,状态的变更放到state里,state自动转换+处理对应逻辑
- 允许一个对象在其内部状态改变时改变他的行为。
- 适用于
- 一个对象的行为取决于它的状态,它必须在运行时刻根据状态改变他的行为
- 一个操作中含有庞大的多分支条件语句,且这些分支依赖于该对象的状态
策略模式
策略模式将流程式的判断变成了内存中储存各个策略,运行时判定取哪个策略再拿出来执行,从而达到了策略和业务逻辑分离的目的。 策略接口-->策略类。策略类定义了相同名的算法。 上下文类,储存策略类的实例化并提供方法进行执行。 客户端,获取策略类,实例化后传入上下文,调用上下文方法从而执行策略的方法。运行时可动态切换策略实例达到更换的目的
如果策略多可以再使用工厂模式 生产策略类
- 定义一系列算法,将他们一个个封装起来,并且使他们可以相互替换。
- 适用于
- 许多相关的类仅仅是行为有异
- 当需要使用一个算法的不同变体
- 算法使用客户不应该知道的数据
- 一个类定义了多种行为
模版方法【类】
模板模式 通过抽象类将总体流程方法 及 部分固有方法实现好,将总体流程方法定义为final不可变,需要调整的方法写为抽象abstract,等待子类来具体实现。还可通过重写hook方法(预定义好,可不重写)来修改父类固有方法的逻辑分支。
- 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。
Template Method使得子类可以不改变一个算法的结构即可重定义该算法的特定步骤 - 适用于
- 一次性实现一个算法的不变部分,将可变行为留给子类来实现
- 各子类公共的行为应被提取出来并集中到一个公共父类中,以免代码重复
- 控制子类扩展,只允许在特定点进行扩展。
访问者模式
访问者模式是一种行为型设计模式,核心在于将数据操作与数据结构分离。它允许在不修改已有对象结构的前提下,为对象结构中的元素添加新操作。 Visitor决定了怎么去看一个Document document.accept(visitor) = visitor.visit(document) 这样处理保证了是文档本身发起预览操作 document具有 增加、发起预览文档的功能。visitor只实现预览方式。
表示一个作用于某对象结构中的各元素的操作。在不改变各元素的类的前提下,定义作用于这些元素的新操作。
适用于
- 一个对象结构包含很多类对象,他们有不同的接口
- 需要对一个对象结构中的对象进行很多不同且不相关的操作。
- 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作