Java 常用设计模式
是在软件设计中被反复使用的一种代码设计经验。其目的是为了可重用代码,提高代码的可扩展性和可维护性。
设计模式主要是基于 OOP 编程,主要是依赖以下几个原则:
开闭原则:即增加功能只增加代码,不修改原有代码
里氏替换原则:即调用父类的方法能够成功,调用子类应该也能够运行
常用的设计模式有 23 个,分为创建型模式、结构型模式和行为型模式
创建型模式
创建型模式关注点是如何创建对象,其核心思想是要把对象的创建和使用相分离,这样使得两者能相对独立地变换
工厂方法
将对象的创建延迟到子类去实现,而不是直接在代码里new。
即通过定义一个用于创建对象的工厂接口,让子类决定实例化哪个类,从而实现对象创建的解耦。
结构:
-
Product(抽象产品)定义产品的公共接口。
-
ConcreteProduct(具体产品)实现
Product接口的具体类。 -
Creator(抽象工厂)声明工厂方法
createProduct(),返回Product类型。可以包含一些默认实现或者核心逻辑。 -
ConcreteCreator(具体工厂)实现
createProduct()方法,返回具体的ConcreteProduct对象。
工厂方法符合开闭原则,增加新产品只需增加新的 ConcreteProduct 和 ConcreteCreator,无需修改现有代码
实现代码
public class Demo {
public static void main(String[] args) {
CarFactory factory = new BenzFactory();
Car car = factory.createCar();
car.drive(); // 输出:驾驶奔驰汽车
factory = new BMWFactory();
car = factory.createCar();
car.drive(); // 输出:驾驶宝马汽车
}
}
// 抽象产品
interface Car {
void drive();
}
// 具体产品
class BenzCar implements Car {
public void drive() {
System.out.println("驾驶奔驰汽车");
}
}
class BMWCar implements Car {
public void drive() {
System.out.println("驾驶宝马汽车");
}
}
abstract class CarFactory {
public abstract Car createCar();
}
// 具体工厂
class BenzFactory extends CarFactory {
public Car createCar() {
return new BenzCar();
}
}
class BMWFactory extends CarFactory {
public Car createCar() {
return new BMWCar();
}
}
和多态的区别:
使用多太时,客户端是知道具体的产品的,需要手动new
Car car = new BenzCar(); car.drive();
而工厂模式中,将具体产品的实现交由工厂来执行
CarFactory factory = new BenzFactory(); Car car = factory.createCar(); car.drive();
静态工厂方法:用类的static方法来创建对象,而不是用new构造器。它把new的逻辑封装在一个静态方法里,对外只暴露一个友好的方法。
比如常见的Integer.of(1)就是静态工厂方法,还有使用MessageDigest时,为了创建某个摘要算法,总是使用静态工厂方法getInstance(String):
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
通过静态工厂方法可以对一开始的实例代码进一步屏蔽细节
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
CarFactory factory = CarFactory.getFactory("BenFactory");
Car car = factory.createCar();
car.drive(); // 输出:驾驶奔驰汽车
factory = CarFactory.getFactory("BMWFactory");
car = factory.createCar();
car.drive(); // 输出:驾驶宝马汽车
}
}
abstract class CarFactory {
public abstract Car createCar();
public static CarFactory getFactory(String factoryClassName)
throws Exception {
return (CarFactory) Class.forName(factoryClassName).getDeclaredConstructor().newInstance();
}
}
抽象工厂
一个工厂负生产多个产品族的对象。即它只提供一个接口,用于创建多个产品,但是不指定这些产品的具体实现。
结构:
-
AbstractFactory(抽象工厂)声明一组用于创建不同产品的方法。
-
ConcreteFactory(具体工厂)实现抽象工厂接口,负责生产某一具体“产品族”的对象。
-
AbstractProduct(抽象产品)为每种产品声明接口。
-
ConcreteProduct(具体产品)具体工厂生产的具体对象。
AbstractFactory / \ ConcreteFactory1 ConcreteFactory2 | \ | \ ProductA1 ProductB1 ProductA2 ProductB2
代码实例
public class Demo {
public static void main(String[] args) {
CarFactory factory = new BenzFactory();
Car car = factory.createCar();
Engine engine = factory.createEngine();
car.drive(); // 驾驶奔驰汽车
engine.run(); // 奔驰发动机运转
factory = new BMWFactory();
car = factory.createCar();
engine = factory.createEngine();
car.drive(); // 驾驶宝马汽车
engine.run(); // 宝马发动机运转
}
}
abstract class CarFactory {
public abstract Car createCar();
public abstract Engine createEngine();
}
class BenzFactory extends CarFactory {
public Car createCar() {
return new BenzCar();
}
public Engine createEngine() {
return new BenzEngine();
}
}
class BMWFactory extends CarFactory {
public Car createCar() {
return new BMWCar();
}
public Engine createEngine() {
return new BMWEngine();
}
}
// Benz 系列
class BenzCar implements Car {
public void drive() {
System.out.println("驾驶奔驰汽车");
}
}
class BenzEngine implements Engine {
public void run() {
System.out.println("奔驰发动机运转");
}
}
// BMW 系列
class BMWCar implements Car {
public void drive() {
System.out.println("驾驶宝马汽车");
}
}
class BMWEngine implements Engine {
public void run() {
System.out.println("宝马发动机运转");
}
}
它和工厂方法的区别是:
工厂方法:一个工厂负责一种产品。
抽象工厂:一个工厂负责一整套相关产品。
生成器
将一个复杂对象的构建过程与它的表示分离。它允许同样的构建过程可以创建不同的表示(不同的产品)
把对象的构建步骤抽象出来,由生成器(Builder)控制,最后由导演(Director)组织构建顺序
工厂模式关注造什么产品,而生成器模式关注怎么一步一步造一个复杂产品
结构:
-
Product(产品角色) 要创建的复杂对象。
-
Builder(抽象建造者) 抽象接口,声明创建各个部件的方法。
-
ConcreteBuilder(具体建造者) 实现 Builder 接口,构建并装配各部件,最后返回产品。
-
Director(指挥者) 构建一个使用 Builder 接口的对象的顺序和逻辑。
实例:
public class Demo {
public static void main(String[] args) {
Director director = new Director();
CarBuilder benzBuilder = new BenzBuilder();
director.construct(benzBuilder);
Car benz = benzBuilder.getCar();
benz.show();
CarBuilder bmwBuilder = new BMWBuilder();
director.construct(bmwBuilder);
Car bmw = bmwBuilder.getCar();
bmw.show();
}
}
// 要创建的产品角色
class Car {
private String engine;
private String wheel;
private String seat;
public void setEngine(String engine) { this.engine = engine; }
public void setWheel(String wheel) { this.wheel = wheel; }
public void setSeat(String seat) { this.seat = seat; }
public void show() {
System.out.println("汽车配置:engine=" + engine + ", wheel=" + wheel + ", seat=" + seat);
}
}
// 抽象构建者
abstract class CarBuilder {
protected Car car = new Car();
public abstract void buildEngine();
public abstract void buildWheel();
public abstract void buildSeat();
public Car getCar() { return car; }
}
// 具体建造者
class BenzBuilder extends CarBuilder {
public void buildEngine() { car.setEngine("奔驰发动机"); }
public void buildWheel() { car.setWheel("奔驰轮胎"); }
public void buildSeat() { car.setSeat("奔驰座椅"); }
}
class BMWBuilder extends CarBuilder {
public void buildEngine() { car.setEngine("宝马发动机"); }
public void buildWheel() { car.setWheel("宝马轮胎"); }
public void buildSeat() { car.setSeat("宝马座椅"); }
}
// 导演
class Director {
public void construct(CarBuilder builder) {
builder.buildEngine();
builder.buildWheel();
builder.buildSeat();
}
}
生成器模式就是把复杂对象的创建过程抽象出来,由建造者负责具体建造,导演负责构建顺序,让客户端只拿最终产品即可。
也可以将 Director 部分融入 Builder 部分。
如 Java 中的 StringBuffer 就是使用了构建者模式;Spring 中的 RestTemplateBuilder、WebClient.Builder 等都是 Builder 风格
WebClient client = WebClient.builder()
.baseUrl("http://example.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
上面这种方式不仅将 Director 隐含在 Builder 内,客户端还通过链式方法配置各个属性,最后 build() 获取产品
上面的实例代码也可以这样更改
public class Demo {
public static void main(String[] args) {
Car car = new CarBuilder()
.setEngine("奔驰发动机")
.setWheel("奔驰轮胎")
.setSeat("奔驰座椅")
.build();
car.show();
}
}
// 要创建的产品角色
class Car {
private String engine;
private String wheel;
private String seat;
public void setEngine(String engine) { this.engine = engine; }
public void setWheel(String wheel) { this.wheel = wheel; }
public void setSeat(String seat) { this.seat = seat; }
public void show() {
System.out.println("汽车配置:engine=" + engine + ", wheel=" + wheel + ", seat=" + seat);
}
}
// 抽象构建者
class CarBuilder {
private Car car = new Car(); // 初始化
public CarBuilder setEngine(String engineName) {
car.setEngine(engineName);
return this; // 返回 builder 本身,才能链式调用
}
public CarBuilder setWheel(String wheelName) {
car.setWheel(wheelName);
return this;
}
public CarBuilder setSeat(String seatName) {
car.setSeat(seatName);
return this;
}
public Car build() {
return car;
}
}
原型
通过复制已有的对象来创建新的对象,而不是通过new关键字来实例化。将对象作为原型,通过克隆(clone)来生成新的对象。
对象的创建成本低,尤其适用于对象初始化复杂或耗时的场景。
模式结构:
-
Prototype(原型接口/抽象类) 声明克隆方法(通常是
clone()) -
ConcretePrototype(具体原型) 实现克隆方法,返回自身的副本
Java 中有两种克隆方式
-
浅克隆:使用
Object.clone()实现,只复制对象的基本字段和引用对象的引用地址。引用对象不会被复制,多个对象共享同一个引用对象 -
不仅复制对象本身,还复制对象中引用的所有对象
Java 中运用原型模式典型的就是Object.clone()
结构型模式
结构型模式主要涉及如何组合各种对象以便获得更好、更灵活的结构。虽然面向对象的继承机制提供了最基本的子类扩展父类的功能,但结构型模式不仅仅简单地使用继承,而更多地通过组合与运行期的动态组合来实现更灵活的功能
适配器
将一个类的接口转换成客户端期望的另一种接口,使原本由于接口不兼容而不能一起工作的类可以一起工作
结构:
-
Target(目标接口) 客户端期望使用的接口
-
Adapter(适配器) 实现 Target 接口,并持有 Adaptee 对象,将请求转换为 Adaptee 的方法调用
-
Adaptee(被适配者) 已有接口或现有功能类
实例
通过继承实现类适配器
// 目标接口
interface Target {
void request();
}
// 被适配者
class Adaptee {
public void specificRequest() {
System.out.println("被适配者的方法被调用");
}
}
// 类适配器
class Adapter extends Adaptee implements Target {
public void request() {
super.specificRequest(); // 转换调用
}
}
// 客户端
public class Demo {
public static void main(String[] args) {
Target target = new Adapter();
target.request(); // 输出:被适配者的方法被调用
}
}
通过组合实现对象适配器
interface Target {
void request();
}
// 对象适配器
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest(); // 转换调用
}
}
// 客户端
public class Demo {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request(); // 输出:被适配者的方法被调用
}
}
桥接
将抽象部分与它的实现部分分离,使它们可以独立变化
结构:
-
Abstraction:定义抽象接口,并持有 Implementor 的引用。
-
RefinedAbstraction:扩展抽象,增加更多行为。
-
Implementor:实现接口,定义具体操作。
-
ConcreteImplementor:具体实现类,实现接口方法。
Abstraction(抽象类) | |--RefinedAbstraction(扩展抽象类) | |-----> Implementor(实现接口) | |--ConcreteImplementorA |--ConcreteImplementorB
桥接模式的优点是抽象与实现分离,提高灵活性;使得系统可独立扩展抽象和实现部分;避免类爆炸(继承层次过深问题)。
实例:
// 实现部分接口
interface Color {
void applyColor();
}
// 具体实现类
class RedColor implements Color {
public void applyColor() {
System.out.println("Applying Red color");
}
}
class BlueColor implements Color {
public void applyColor() {
System.out.println("Applying Blue color");
}
}
// 抽象部分
abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
abstract void draw();
}
// 扩展抽象类
class Circle extends Shape {
public Circle(Color color) { super(color); }
public void draw() {
System.out.print("Drawing Circle with ");
color.applyColor();
}
}
class Square extends Shape {
public Square(Color color) { super(color); }
public void draw() {
System.out.print("Drawing Square with ");
color.applyColor();
}
}
public class Demo {
public static void main(String[] args) {
Shape redCircle = new Circle(new RedColor());
redCircle.draw();
Shape blueSquare = new Square(new BlueColor());
blueSquare.draw();
}
}
这样,让抽象部分的 Shape 持有 Color 接口的引用来实现桥接。当 Color 扩展了新的子类后,不影响 Shape 的实现类拿到他。Shape 的实现类也不影响 Color 扩展。
组合
将对象组合成树形结构表示“整体-部分”层次,让客户端对单个对象和组合对象使用一致的方式
结构:
-
Component:抽象接口,定义统一操作。
-
Leaf:叶子节点,不包含子节点。
-
Composite:组合节点,包含子节点,可以添加、删除、遍历子节点。
Component(抽象组件) | |--Leaf(叶子节点) | |--Composite(组合节点) | |--Leaf/Composite...
java 实例代码:
// 抽象组件
abstract class FileSystemNode {
protected String name;
public FileSystemNode(String name) {
this.name = name;
}
abstract void show(int indent); // 显示节点信息,带缩进
}
// 文件节点
class FileNode extends FileSystemNode {
public FileNode(String name) {
super(name);
}
@Override
void show(int indent) {
System.out.println(" ".repeat(indent) + "File: " + name);
}
}
// 目录节点
class FolderNode extends FileSystemNode {
private List<FileSystemNode> children = new ArrayList<>();
public FolderNode(String name) {
super(name);
}
public void add(FileSystemNode node) {
children.add(node);
}
public void remove(FileSystemNode node) {
children.remove(node);
}
@Override
void show(int indent) {
System.out.println(" ".repeat(indent) + "Folder: " + name);
for (FileSystemNode node : children) {
node.show(indent + 1); // 递归调用子节点
}
}
}
public class FileSystemDemo {
public static void main(String[] args) {
FileSystemNode file1 = new FileNode("file1.txt");
FileSystemNode file2 = new FileNode("file2.txt");
FolderNode folder1 = new FolderNode("Documents");
folder1.add(file1);
folder1.add(file2);
FileSystemNode file3 = new FileNode("file3.txt");
FolderNode folder2 = new FolderNode("Downloads");
folder2.add(file3);
FolderNode root = new FolderNode("Root");
root.add(folder1);
root.add(folder2);
root.show(0);
}
}
其中 FileSystemNode 是抽象接口 Component,扮演叶子节点 FileNode 组合节点 FolderNode 的统一抽象角色。让客户端对单个对象和组合对象使用一致的方式。
一个组合节点可以持有多个叶子节点,也可以持有组合节点。因为它们都继承自基节点 Component,使得在操作节点时只需关注是否是 Component 就可以了。体现了组合模式的核心思想:部分和整体可以统一对待
装饰器
在不修改原有对象结构的情况下,动态地给对象添加新的功能
结构
-
Component:定义对象的接口
-
ConcreteComponent:被装饰的原始对象
-
Decorator:持有 Component 引用,实现 Component 接口
-
ConcreteDecorator:具体装饰器,增加新的行为
Component(抽象组件) | |--ConcreteComponent(具体组件) | |--Decorator(抽象装饰类) | |--ConcreteDecorator(具体装饰类)
// 对象接口
interface Coffee {
String getDescription();
double cost();
}
// 具体组件
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double cost() {
return 5.0;
}
}
// 抽象装饰器
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
public String getDescription() {
return coffee.getDescription();
}
public double cost() {
return coffee.cost();
}
}
// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double cost() {
return coffee.cost() + 2.0;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Sugar";
}
public double cost() {
return coffee.cost() + 1.0;
}
}
在不修改原有 SimpleCoffee 具体组件的基础上,让抽象装饰器持有抽象组件接口,通过实现具体的装饰器来实现功能的增加
外观
客户端不需要直接和复杂的子系统交互,外观模式提供一个“外观类”作为简化入口,起到对外屏蔽内部复杂性的作用
结构:
Client:只和 Facade 打交道
Facade:封装子系统调用,提供简单接口
Subsystem:实现复杂逻辑,但客户端不直接访问
Client(客户端) | v Facade(外观类) / | \ v v v SubsystemA SubsystemB SubsystemC(子系统类)
实例
// 子类系统
class CPU {
void freeze() { System.out.println("CPU freeze"); }
void execute() { System.out.println("CPU execute"); }
}
class Memory {
void load(long position, String data) {
System.out.println("Memory load data: " + data + " at position " + position);
}
}
class HardDrive {
String read(long lba, int size) {
return "DataFromDisk";
}
}
// 外观类
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
public void start() {
System.out.println("Starting computer...");
cpu.freeze();
String data = hardDrive.read(0, 1024);
memory.load(0, data);
cpu.execute();
System.out.println("Computer started!");
}
}
享元
通过共享对象来减少内存消耗,避免大量拥有相同内容的对象重复创建。
将对象状态分为 内部状态(可以共享、不随环境变化)和 外部状态(不可共享、随环境变化)。内部状态存放在享元对象中,共享使用;外部状态由客户端维护,在使用时传入。
在 Java 中,像 String 常量池,Integer 缓存,数据库连接池就是类似的模式
结构:
Flyweight:享元接口
ConcreteFlyweight:具体实现,可共享
FlyweightFactory:负责存储和管理享元对象池
实例:
// 享元接口
interface Font {
void display(String content); // 外部状态由调用方传入
}
// 具体实现类
class ConcreteFont implements Font {
private final String fontName; // 内部状态(共享)
public ConcreteFont(String fontName) {
this.fontName = fontName;
}
@Override
public void display(String content) {
System.out.println("[" + fontName + "] " + content);
}
}
// 享元工厂
class FontFactory {
private static final Map<String, Font> fontPool = new HashMap<>();
public static Font getFont(String fontName) {
if (!fontPool.containsKey(fontName)) {
fontPool.put(fontName, new ConcreteFont(fontName));
System.out.println("Created new Font: " + fontName);
}
return fontPool.get(fontName);
}
}
享元接口对外暴露统一的访问方法。客户端通过这个接口访问共享数据。本身不存储外部状态,只定义行为。
具体享元类(ConcreteFlyweight)持有内部状态(共享数据),这些数据在对象创建后不会改变。实现享元接口,对外提供具体功能。不关心外部状态,外部状态由客户端在调用时传入。
享元工厂(FlyweightFactory)负责管理和维护一个共享池(对象缓存池)。确保相同的内部状态只会创建一次,并复用对象。工厂方法通常是静态方法,持有一个 Map<K, Flyweight>。
代理
为某个对象提供一个代理对象,由代理对象来控制对真实对象的访问。代理对象和真实对象对外暴露的接口一致。
客户端通过代理访问真实对象,代理可以在调用前后附加额外逻辑。
结构:
-
Subject(抽象主题)定义了真实对象和代理对象的共同接口,客户端只依赖这个接口。
-
RealSubject(真实主题)实现具体业务逻辑,是客户端真正需要访问的对象。
-
Proxy(代理对象)持有
RealSubject的引用。在调用前后执行附加操作(权限控制、缓存、日志、远程调用等)。
常见的代理模式分三种:
-
静态代理:在编译时就确定代理类,手写或生成。
-
动态代理(JDK):运行时通过反射生成代理类(接口)。
-
CGLIB 代理:运行时通过字节码技术为类生成子类代理(没有接口时也能用)。
实例:
// 抽象主题
interface RentHouse {
void rent();
}
// 真实主题
class Landlord implements RentHouse {
@Override
public void rent() {
System.out.println("房东:把房子租出去!");
}
}
// 代理类,静态代理
class Agent implements RentHouse {
private Landlord landlord;
public Agent(Landlord landlord) {
this.landlord = landlord;
}
@Override
public void rent() {
System.out.println("中介:收取中介费");
landlord.rent();
System.out.println("中介:提供后续服务");
}
}
// 动态代理
class RentHandler implements InvocationHandler {
private Object target;
public RentHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中介:收取中介费");
Object result = method.invoke(target, args); // 调用真实对象
System.out.println("中介:提供后续服务");
return result;
}
}
public class DynamicProxyDemo {
public static void main(String[] args) {
Landlord landlord = new Landlord();
RentHouse proxy = (RentHouse) Proxy.newProxyInstance(
landlord.getClass().getClassLoader(),
new Class[]{RentHouse.class},
new RentHandler(landlord));
proxy.rent();
}
}
装饰器模式和代理模式的区别:
-
装饰器是增强功能,重点在“扩展”
-
代理是控制访问,重点在“代理对象代替真实对象做一些事”
静态代理和动态代理的区别:
-
静态代理是编译期确定代理类,它需要为每一个需要代理的类都实现一个代理类(代理类和真实类实现同一个接口,内部调用真实对象的方法),代码冗余
-
动态代理是运行时通过反射生成的代理类,不需要手动实现。但是只能代理接口,不能直接代理类。
行为型模式
行为型模式主要涉及算法和对象间的职责分配。通过使用对象组合,行为型模式可以描述一组对象应该如何协作来完成一个整体任务
责任链
将多个处理对象(Handler)按顺序串联起来,使请求在链上传递,直到有一个处理对象能够处理为止
请求发送者和处理者解耦,避免了请求者与多个处理者之间的紧耦合
结构:
-
抽象处理者(Handler)定义一个处理请求的接口,保存一个后继者(即“下一个处理者”)的引用
-
具体处理者(ConcreteHandler)实现具体的处理逻辑,如果自己不能处理,就把请求交给下一个处理者
实例:
// 抽象处理者
abstract class Handler {
protected Handler next; // 下一个处理者
public void setNext(Handler next) {
this.next = next;
}
public abstract void handleRequest(int days);
}
// 具体处理者:组长
class Leader extends Handler {
public void handleRequest(int days) {
if (days <= 2) {
System.out.println("组长批准 " + days + " 天假");
} else if (next != null) {
next.handleRequest(days);
}
}
}
// 具体处理者:经理
class Manager extends Handler {
public void handleRequest(int days) {
if (days <= 5) {
System.out.println("经理批准 " + days + " 天假");
} else if (next != null) {
next.handleRequest(days);
}
}
}
// 具体处理者:总经理
class GeneralManager extends Handler {
public void handleRequest(int days) {
if (days > 5) {
System.out.println("总经理批准 " + days + " 天假");
}
}
}
// 客户端
public class Demo {
public static void main(String[] args) {
Handler leader = new Leader();
Handler manager = new Manager();
Handler gm = new GeneralManager();
// 组装责任链
leader.setNext(manager);
manager.setNext(gm);
// 测试
leader.handleRequest(1); // 组长批准
leader.handleRequest(3); // 经理批准
leader.handleRequest(10); // 总经理批准
}
}
责任链模式需要正确组装责任链,否则会出现错误
可以在一个构建链器中统一组装责任链,以免出现漏掉处理者的情况
// 链构建器:统一负责组装链条
class ChainBuilder {
public static Handler buildChain() {
Handler leader = new Leader();
Handler manager = new Manager();
Handler gm = new GeneralManager();
leader.setNext(manager);
manager.setNext(gm);
return leader; // 返回链头
}
}
该模式有很多应用场景:
Java Servlet 过滤器链(FilterChain)
SpringMVC HandlerInterceptor
日志处理链(不同日志级别的处理器)
OA 系统的审批流
命令
将一个请求封装为一个对象,从而使你可以用不同的请求、队列或日志来参数化对象
-
命令接口(Command)定义一个统一的执行方法,比如
execute()
-
具体命令(ConcreteCommand)封装接收者(Receiver)调用的动作
-
接收者(Receiver)真正执行请求逻辑的对象
-
调用者(Invoker)持有命令对象,调用命令的
execute()方法
// 命令接口
interface Command {
void execute();
}
// 接收者:灯
class Light {
public void on() {
System.out.println("灯打开了");
}
public void off() {
System.out.println("灯关闭了");
}
}
// 接收者:电视
class TV {
public void on() {
System.out.println("电视打开了");
}
public void off() {
System.out.println("电视关闭了");
}
}
// 具体命令:打开灯
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
}
// 具体命令:关闭灯
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
}
// 具体命令:打开电视
class TVOnCommand implements Command {
private TV tv;
public TVOnCommand(TV tv) {
this.tv = tv;
}
public void execute() {
tv.on();
}
}
// 具体命令:关闭电视
class TVOffCommand implements Command {
private TV tv;
public TVOffCommand(TV tv) {
this.tv = tv;
}
public void execute() {
tv.off();
}
}
// 调用者:遥控器
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
// 客户端
public class CommandPatternDemo {
public static void main(String[] args) {
Light light = new Light();
TV tv = new TV();
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
Command tvOn = new TVOnCommand(tv);
Command tvOff = new TVOffCommand(tv);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton(); // 灯打开了
remote.setCommand(tvOn);
remote.pressButton(); // 电视打开了
remote.setCommand(lightOff);
remote.pressButton(); // 灯关闭了
remote.setCommand(tvOff);
remote.pressButton(); // 电视关闭了
}
}
请求不是直接用接收者调用,而是先被封装成一个命令对象。这个命令对象实现了统一的 Command 接口,并且内部保存了“接收者”的引用,通过重写命令接口的方法,在里面调用接收者对应的请求。调用者 (Invoker) 不需要知道命令具体怎么执行,也不需要知道接收者是谁,只要调用 command.execute() 就行。接收者 (Receiver) 只负责执行具体业务逻辑。
这样就实现了请求的发送者(Invoker)和接收者(Receiver)解耦。
解释器
给定一种语言,定义它的文法的一种表示,并定义一个解释器来解释这个语言中的句子。
即把某种规则(语法/表达式)抽象成类结构,并通过递归调用对象来解释语句
结构:
-
抽象表达式(AbstractExpression)定义解释操作的接口,比如
interpret(Context context)
-
终结符表达式(TerminalExpression)处理文法规则中的最小单元(比如数字、变量)
-
非终结符表达式(NonTerminalExpression)处理由终结符或其他非终结符组合的复杂规则(比如加法、减法表达式)
-
上下文(Context)提供解释器需要的数据或变量值
比如
-
数据库并不认识 SQL 字符串。而通过SQL 解析器可以把 SQL 字符串解释成数据库能执行的操作。
-
正则表达式解释器可以把正则规则解释成一个匹配引擎,逐步对输入字符串进行匹配。
这两者都体现了解释器模式的本质: 把“语言规则”对象化,用解释器逐步解析执行。
中介
用一个中介对象来封装一系列对象之间的交互,使各对象不直接引用彼此,从而降低耦合。即当多个对象之间存在复杂交互时,不让它们互相通信,而是通过一个“中介”来协调。
结构:
-
抽象中介者(Mediator)定义同事对象通信的接口
-
具体中介者(ConcreteMediator)实现具体协调逻辑。持有同事对象的引用,负责协调它们之间的行为
-
同事对象(Colleague)不直接与其他同事对象通信。与中介者保持引用,通过中介者发送和接收信息
// 抽象中介者
interface Mediator {
void send(String message, Colleague colleague);
}
// 抽象同事类
abstract class Colleague {
protected Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
}
// 具体同事:房东
class Landlord extends Colleague {
public Landlord(Mediator mediator) { super(mediator); }
public void send(String message) {
System.out.println("房东发送消息: " + message);
mediator.send(message, this);
}
public void notify(String message) {
System.out.println("房东收到消息: " + message);
}
}
// 具体同事:租客
class Tenant extends Colleague {
public Tenant(Mediator mediator) { super(mediator); }
public void send(String message) {
System.out.println("租客发送消息: " + message);
mediator.send(message, this);
}
public void notify(String message) {
System.out.println("租客收到消息: " + message);
}
}
// 具体中介者
class Agent implements Mediator {
private Landlord landlord;
private Tenant tenant;
public void setLandlord(Landlord landlord) { this.landlord = landlord; }
public void setTenant(Tenant tenant) { this.tenant = tenant; }
public void send(String message, Colleague colleague) {
if (colleague == landlord) {
tenant.notify(message);
} else if (colleague == tenant) {
landlord.notify(message);
}
}
}
// 客户端
public class MediatorDemo {
public static void main(String[] args) {
Agent agent = new Agent();
Landlord landlord = new Landlord(agent);
Tenant tenant = new Tenant(agent);
agent.setLandlord(landlord);
agent.setTenant(tenant);
tenant.send("我想租房子");
landlord.send("房子已经出租了");
}
}
具体同事类中定义发送消息的方法和接收消息的方法。同事类中持有中介者类的实现用以互相通信,这依靠的事中介者类定义一个方法,该方法将被同事类调用,用来通知对应的同事类。
中介者类中也持有相关同事类的引用。
这样就降低对象之间的耦合度。有利于集中控制通信逻辑,易于维护和修改。
扩展性好,只需修改中介者即可支持新同事对象。
备忘录
它允许在不暴露对象实现细节的情况下,保存和恢复对象的内部状态。它常用于实现撤销/回滚操作
结构:
-
发起人(Originator)需要保存和恢复状态的对象。提供创建和恢复备忘录的接口。
-
备忘录(Memento)存储发起人的内部状态。通常只允许发起人访问。
-
管理者(Caretaker) 保存备忘录,不操作备忘录内容,只负责保存和提供备忘录给发起人。
实例
// 备忘录类
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// 发起人
class Originator {
private String state;
public void setState(String state) {
this.state = state;
System.out.println("State set to: " + state);
}
public Memento createMemento() {
return new Memento(state);
}
public void restore(Memento memento) {
this.state = memento.getState();
System.out.println("State restored to: " + state);
}
}
// 管理者
class Caretaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento memento) {
mementoList.add(memento);
}
public Memento get(int index) {
return mementoList.get(index);
}
}
// 测试
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("State1");
caretaker.add(originator.createMemento());
originator.setState("State2");
caretaker.add(originator.createMemento());
originator.setState("State3");
// 恢复到第一个状态
originator.restore(caretaker.get(0)); // 输出: State restored to: State1
}
}
实例化一个备忘录类,定义一个和发起人中需要记录的字段的一个相同字段。发起人类中可以定义一个方法,用来开启备忘录,同时也要定义一个方法用来恢复之前记录的备忘录。这个备忘录需要由一个管理者记录。发起人就是从这个管理者得到需要恢复的状态的。
观察者
用于实现对象之间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都会得到通知并自动更新。它是实现事件监听机制的基础
结果:
-
主题(Subject) 维护观察者列表,提供注册、删除、通知观察者的方法。
-
观察者(Observer) 定义响应主题变化的接口,实现具体更新逻辑。
实例:
// 观察者接口
interface Observer {
void update(String message);
}
// 主题接口
interface Subject {
void attach(Observer o);
void detach(Observer o);
void notifyObservers();
}
// 具体主题
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String state;
public void setState(String state) {
this.state = state;
notifyObservers();
}
public String getState() {
return state;
}
@Override
public void attach(Observer o) {
observers.add(o);
}
@Override
public void detach(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer o : observers) {
o.update(state);
}
}
}
// 具体观察者
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received update: " + message);
}
}
// 测试
public class ObserverPatternDemo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer obs1 = new ConcreteObserver("Observer1");
Observer obs2 = new ConcreteObserver("Observer2");
subject.attach(obs1);
subject.attach(obs2);
subject.setState("State Changed!");
// 输出:
// Observer1 received update: State Changed!
// Observer2 received update: State Changed!
}
}
在这里,观察者就是被动被通知相关类被改变的类。主题是主动通知的一方,主题也维护了一个需要通知类的列表。如果主题类有什么变化,就会调用相关方法通知观察者什么发生了改变。
状态
它允许对象在内部状态发生变化时改变它的行为,看起来就像对象的类在运行时发生了改变。它常用于状态机设计
结构:
-
环境(Context)持有一个状态对象,负责将请求委托给当前状态处理。
-
抽象状态(State)定义一个接口以封装与环境的一个状态相关的行为。
-
具体状态(ConcreteState)实现抽象状态接口的具体行为,根据情况切换环境的状态。
实例:
// 抽象状态
interface State {
void handle(Context context);
}
// 具体状态A
class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("Handling in State A, switching to State B");
context.setState(new ConcreteStateB());
}
}
// 具体状态B
class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("Handling in State B, switching to State A");
context.setState(new ConcreteStateA());
}
}
// 环境
class Context {
private State state;
public Context(State state) {
this.state = state;
}
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}
// 测试
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context(new ConcreteStateA());
context.request(); // 输出: Handling in State A, switching to State B
context.request(); // 输出: Handling in State B, switching to State A
context.request(); // 输出: Handling in State A, switching to State B
}
}
将状态和状态相关行为封装,避免大量条件判断。让上下文环境持有当前状态,让每个状态执行自己相应的行为和决定之后的状态变换。
策略
用于定义一系列算法,把每个算法封装起来,使它们可以互换,算法的变化不会影响使用算法的客户。它常用于需要在运行时选择算法的场景
结构:
-
抽象策略(Strategy) 定义支持所有算法的接口。
-
具体策略(ConcreteStrategy) 实现具体的算法或业务逻辑。
-
上下文(Context) 持有策略对象的引用,调用策略接口执行算法。
实例:
// 抽象策略
interface Strategy {
int doOperation(int a, int b);
}
// 具体策略A:加法
class OperationAdd implements Strategy {
@Override
public int doOperation(int a, int b) {
return a + b;
}
}
// 具体策略B:减法
class OperationSubtract implements Strategy {
@Override
public int doOperation(int a, int b) {
return a - b;
}
}
// 上下文
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int a, int b) {
return strategy.doOperation(a, b);
}
}
// 测试
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context.setStrategy(new OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
}
}
每个算法都封装在一个独立类里(即 ConcreteStrategy),实现统一的策略接口(Strategy)。算法类只关心自己的实现细节,不关心其他算法,也不关心客户端如何调用。算法之间互相独立,便于扩展和维护。
客户端可以自由选择算法的实现,客户端不直接写条件语句去判断使用哪种算法,而是通过 Context 或直接持有策略接口引用来选择算法。
模板方法
用于定义一个操作的算法骨架,而将一些步骤延迟到子类实现。它让子类在不改变算法整体结构的前提下重新定义算法的某些步骤
结构:
-
抽象类(AbstractClass) 定义模板方法,包含算法骨架和若干抽象方法或可覆盖的钩子方法。
-
具体子类(ConcreteClass) 实现抽象类中定义的抽象方法,完成算法的具体步骤。
实例:
// 抽象模板类
abstract class Game {
// 模板方法,定义算法骨架
public final void play() {
initialize();
startPlay();
endPlay();
}
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
}
// 具体子类1
class Cricket extends Game {
@Override
void initialize() {
System.out.println("Cricket Game Initialized!");
}
@Override
void startPlay() {
System.out.println("Cricket Game Started!");
}
@Override
void endPlay() {
System.out.println("Cricket Game Finished!");
}
}
// 具体子类2
class Football extends Game {
@Override
void initialize() {
System.out.println("Football Game Initialized!");
}
@Override
void startPlay() {
System.out.println("Football Game Started!");
}
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
}
这样流程由父类控制,而子类用以实现算法的具体步骤
访问者
用于在不改变对象结构的前提下,为对象结构中的元素定义新的操作。它特别适用于对象结构固定,但操作经常变化的场景
结构:
-
访问者(Visitor) 定义对每个具体元素访问时的操作接口。
-
具体访问者(ConcreteVisitor) 实现访问者接口,定义具体操作逻辑。
-
元素(Element) 定义接受访问者的接口(
accept(Visitor v))。 -
具体元素(ConcreteElement) 实现元素接口,调用访问者的相应方法。
-
对象结构(ObjectStructure) 可以是集合或复合结构,负责管理元素并提供遍历接口。
实例:
// 元素接口
interface Element {
void accept(Visitor visitor);
}
// 具体元素A
class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "ConcreteElementA";
}
}
// 具体元素B
class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "ConcreteElementB";
}
}
// 访问者接口
interface Visitor {
void visit(ConcreteElementA elementA);
void visit(ConcreteElementB elementB);
}
// 具体访问者
class ConcreteVisitor implements Visitor {
@Override
public void visit(ConcreteElementA elementA) {
System.out.println("Visiting " + elementA.operationA());
}
@Override
public void visit(ConcreteElementB elementB) {
System.out.println("Visiting " + elementB.operationB());
}
}
// 对象结构
class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void add(Element e) {
elements.add(e);
}
public void accept(Visitor visitor) {
for (Element e : elements) {
e.accept(visitor);
}
}
}
// 测试
public class VisitorPatternDemo {
public static void main(String[] args) {
ObjectStructure structure = new ObjectStructure();
structure.add(new ConcreteElementA());
structure.add(new ConcreteElementB());
Visitor visitor = new ConcreteVisitor();
structure.accept(visitor);
}
}
具体的元素类内部实现一个方法,用来接受一个访问者类,这个访问者类内部实现了具体的访问元素类内部的元素方法。
这样就实现了访问者在不修改元素类的情况下访问元素内部数据或方法
为什么有这样的说法嗯。
一个封装好的类内部属性是私有的,它只提供有限的共有方法来访问这个属性。访问者通过调用这个有限的共有方法来实现对私有属性的访问,但为什么不直接使用公有方法访问该属性,反而要通过访问者。这是因为直接访问数据或行为会让行为逻辑分散在客户端。如果想增加新的操作,比如“打印元素信息为大写”或“计算元素值的平方”,就必须修改客户端代码。每增加一个操作,客户端都要改,违反 开放-封闭原则(对扩展开放,对修改关闭)。而访问者可以把操作逻辑从元素和客户端分离出来,所有操作都在访问者里实现,客户端无需关心细节,新增操作只需新增一个访问者,不修改元素类。
更多推荐
所有评论(0)