1、创建型模式(5种)
1.1、工厂方法
工厂模式(Factory Pattern)
的主要目的是为了将对象的创建过程延迟到子类中进行决定
1.1.1、创建不同的图形形状
抽象产品(Abstract Product)
/** * 抽象产品(Abstract Product):或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。 */ public interface Shape { // 绘画 定义了产品的共同接口 void draw(); }
实体类
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); } } public class Square implements Shape { @Override public void draw() { System.out.println("Inside Square::draw() method."); } } public class Circle implements Shape { @Override public void draw() { System.out.println("Inside Circle::draw() method."); } }
工厂
public class ShapeFactory { //使用 getShape 方法获取形状类型的对象 public Shape getShape(String shapeType){ if(shapeType == null){ return null; } if(shapeType.equalsIgnoreCase("CIRCLE")){ return new Circle(); } else if(shapeType.equalsIgnoreCase("RECTANGLE")){ return new Rectangle(); } else if(shapeType.equalsIgnoreCase("SQUARE")){ return new Square(); } return null; } }
测试
public static void main(String[] args) { ShapeFactory shapeFactory = new ShapeFactory(); //获取 Circle 的对象,并调用它的 draw 方法 Shape shape1 = shapeFactory.getShape("CIRCLE"); //调用 Circle 的 draw 方法 shape1.draw(); //获取 Rectangle 的对象,并调用它的 draw 方法 Shape shape2 = shapeFactory.getShape("RECTANGLE"); //调用 Rectangle 的 draw 方法 shape2.draw(); //获取 Square 的对象,并调用它的 draw 方法 Shape shape3 = shapeFactory.getShape("SQUARE"); //调用 Square 的 draw 方法 shape3.draw(); }
结果
Inside Circle::draw() method. Inside Rectangle::draw() method. Inside Square::draw() method.
1.1.2、不同类型的鼠标
// 抽象产品 interface Mouse { void click(); } // 具体产品 class DellMouse implements Mouse { @Override public void click() { System.out.println("Dell mouse clicked"); } } class LenovoMouse implements Mouse { @Override public void click() { System.out.println("Lenovo mouse clicked"); } } // 简单工厂类 class MouseFactory { static Mouse createMouse(String brand) { if ("Dell".equals(brand)) { return new DellMouse(); } else if ("Lenovo".equals(brand)) { return new LenovoMouse(); } else { throw new IllegalArgumentException("Invalid brand"); } } } // 使用 Mouse mouse = MouseFactory.createMouse("Dell"); // 或 "Lenovo" mouse.click();
我的理解
- 工厂方法模式,事先定义好了一套解决方案,具体要使用哪一个方案的时候,需要告诉工厂进行创建对应的类返回。
联想一下:
用户登录的时候,前台选择登录的类型,比如传参 loginType = [ phone, account, mail ] , 然后后台根据不同的 loginType 参数,从工厂获取对应的登录实现
1.2、抽象工厂
1.2.1、定义:
抽象工厂模式(Abstract Factory Pattern)
是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
涉及多个系列的产品对象的创建,它提供了创建一族相关或相互依赖对象的接口,而不仅仅是一个产品对象。抽象工厂模式下创建的对象之间往往有关联或一致性约束。
runoob 菜鸟教程中介绍:
https://www.runoob.com/design-pattern/abstract-factory-pattern.html
1、何时使用:系统的产品有多于一个的产品族,而系统只消费其中某一族的产品。
聚会需要穿着的服装,有商务装、时尚装,屌丝装等待,而这写服装下,还有具体的商务男装、商务女装,屌丝男装... 但是聚会今晚心情好,穿一件屌丝男装,不过分吧。 ----- 这就是系统的产品有多于一个的产品族,我只消费其中一个,就要屌丝男装 , 屌丝男装是一系列的包括(衣服、裤子、帽子、鞋子)
refactoringguru例子中的按钮
意图:
一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。
参考 https://refactoringguru.cn/design-patterns/abstract-factory 不同操作系统中的按钮说明
🚩如果客户端仅接触抽象接口, 那么谁来创建实际的工厂对象呢? 一般情况下, 应用程序会在初始化阶段创建具体工厂对象。 而在此之前, 应用程序必须根据配置文件或环境设定选择工厂类别。
1.2.2、产品族和产品等级
继续参考博客: https://blog.csdn.net/qq_33732195/article/details/110101808
该博客中说:
产品族:一个品牌下面的所有产品;例如华为下面的手机,路由器,电脑 称为华为的产品族;
产品等级:多个品牌下面的同种产品;例如华为和小米下面的手 称为一个产品等级;
通义千问 ai
-
产品等级结构(Product Hierarchy)
产品等级结构指的是产品类的继承体系结构,也就是说,这些产品通常会有一个共同的抽象基类,然后有不同的具体实现作为这个基类的派生类。例如,在家电领域,可能会有一个抽象的“电视机”类,而具体的产品等级结构可能包括“液晶电视”、“等离子电视”、“OLED电视”等多个具体品牌或型号的电视机类,它们都继承自抽象电视机类。 -
产品族(Product Family)
产品族是指在同一抽象工厂下创建的一组相关的产品集合。这些产品通常一起工作,满足某种特定的功能需求或环境要求,并且属于不同的产品等级结构。例如,一个家电工厂的产品族可能包括电视机、洗衣机、冰箱等多种产品,每种产品下又有各自的品牌或型号系列。因此,“海尔家电产品族”可能包括海尔品牌的电视机产品线、海尔洗衣机产品线以及海尔冰箱产品线,每个产品线内部有自己的产品等级结构。
1.2.3、举例
基于 https://refactoringguru.cn/design-patterns/abstract-factory 中的例子,椅子、沙发、咖啡桌
超级工厂
public interface AbstractFactory {
IChair createChair();
ICoffeeTable createCoffeeTable();
ISofa createSofa();
}
艺术工厂
public class ArtFactory implements AbstractFactory {
@Override
public IChair createChair() {
return new ArtChair();
}
@Override
public ICoffeeTable createCoffeeTable() {
return new ArtCoffeeTable();
}
@Override
public ISofa createSofa() {
return new ArtSofa();
}
}
艺术椅子基类/接口
public interface IChair {
void chairSitDown();
}
艺术椅子
public class ArtChair implements IChair {
@Override
public void chairSitDown() {
System.out.println("【艺术】椅子,二哈不敢咬。。。");
}
}
AppClient 端测试
public class AppClient {
public static void main(String[] args) {
// AbstractFactory fastory = new MordernFactory();
AbstractFactory fastory = new ArtFactory();
// AbstractFactory fastory = new VictorianFactory();
IChair chair = fastory.createChair();
chair.chairSitDown();
ISofa sofa = fastory.createSofa();
sofa.sofaSitDown();
ICoffeeTable coffeeTable = fastory.createCoffeeTable();
coffeeTable.putDown();
}
}
我的理解:
抽象工厂:工厂的工厂,产生的是一系列的产品。普通的工厂,生产椅子、桌子、沙发,产出的单个对象。现在的需求是需要不同的风格的这样家具,需要欧美风格的椅子、桌子、沙发,所以就是用抽象来获取对应的制造工厂。
1.3、建造者
建造者模式 Builder
定义:
菜鸟教程:
建造者模式是使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。一个Builder类会一步一步构造最终的对象。
意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。
https://refactoringguru.cn/design-patterns/builder 中定义
生成器模式是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。
通义千问:
设计模式之建造者模式
建造者模式(Builder Pattern)是一种创建型设计模式,其核心目标是将复杂对象的构建过程与表示分离,从而实现对复杂对象的构造过程的解耦和抽象,使得同一构建过程能够产生不同的表示形式。在实际应用中,建造者模式尤其适用于需要创建具有多种不同配置或组合方式的复杂对象,且这些对象的构建过程可能包含多个步骤或依赖于多个子部件。
以下是建造者模式的基本概念、组成部分、工作原理以及其主要优点和应用场景:
基本概念
- 复杂对象:指的是包含多个属性或者子部件,且这些属性或子部件之间可能存在复杂的装配关系,直接通过构造函数或简单工厂难以清晰、灵活地创建的对象。
- 构建过程与表示分离:这意味着创建复杂对象的具体步骤(构建过程)被封装在独立的“建造者”类中,而对象本身的类(通常称为产品类)只负责存储和表示这些属性或子部件。这样,客户端代码只需与建造者交互来控制构建过程,而不必关心具体的内部构造细节。
组成部分
建造者模式通常包含以下四个关键角色:
- 产品(Product):定义要构建的复杂对象的接口,包含各个组成部件的声明。具体的产品类实现了这个接口。
- 建造者(Builder):定义了一个用于创建产品对象的接口,声明了用于逐步构建复杂对象的所有方法,一般包括设置各个部件的方法。
- 具体建造者(Concrete Builder):实现建造者接口,提供构建复杂对象各个部件的具体实现。每个具体建造者都对应一种具体的产品配置或表示。
- 导演者(Director):负责协调建造过程,它不直接参与产品的创建,而是调用具体建造者的接口来完成产品的构建。导演者可以根据需要决定调用建造者的构建方法的顺序和次数,确保最终得到完整的产品。
工作原理
- 客户端代码通过导演者指定使用某个具体建造者来构建产品。
- 导演者按照预定的构建流程,调用具体建造者的相关方法来逐步构建产品。
- 具体建造者在内部根据调用顺序和参数,将各个部件装配到产品对象中。
- 构建完成后,具体建造者返回已构建好的产品实例给客户端或导演者。
优点
- 封装性:复杂对象的构建代码被封装在建造者类中,客户端无需了解内部构建细节。
- 灵活性:可以通过创建不同的具体建造者来实现对产品对象的不同配置或表示形式。
- 扩展性:增加新的产品配置或变体时,只需新增一个具体建造者类,对已有代码影响小。
- 一致性:通过导演者角色可以确保即使复杂对象的构建步骤很多,也能以统一的方式创建,避免重复代码。
- 易于并行构建:在某些情况下,建造者模式支持将构建过程分解为独立的任务,便于并行执行。
应用场景
- 对象构造过程复杂,有多步依赖或多种组合方式:如配置计算机硬件(CPU、内存、硬盘等)、组装汽车零部件、生成复杂的XML或JSON文档等。
- 需要生成具有不同表现形式或配置的相似对象:如创建不同口味的披萨、不同规格的报表、不同布局风格的网页等。
- 构建过程独立于产品对象的组成部分:当构建算法与产品类本身无关,或者希望在不修改产品类的情况下改变构建过程时。
总之,建造者模式通过引入独立的建造者类来封装复杂对象的构建过程,提供了一种灵活、可扩展的方式来创建具有复杂内部结构的对象,同时保持了客户端代码的简洁和构建逻辑的复用性。
1.3.1、建造汽车
-
使用外部的导演者来调用建造者
Director导演者,使用构建者构建
public class Director { public void constructSportsCar(Builder builder) { builder.setCarType(CarType.SPORTS_CAR); builder.setSeats(2); builder.setEngine(new Engine(3.0, 0)); builder.setTransmission(Transmission.SEMI_AUTOMATIC); builder.setTripComputer(new TripComputer()); builder.setGPSNavigator(new GPSNavigator()); } public void constructCityCar(Builder builder) { builder.setCarType(CarType.CITY_CAR); builder.setSeats(2); builder.setEngine(new Engine(1.2, 0)); builder.setTransmission(Transmission.AUTOMATIC); builder.setTripComputer(new TripComputer()); builder.setGPSNavigator(new GPSNavigator()); } public void constructSUV(Builder builder) { builder.setCarType(CarType.SUV); builder.setSeats(4); builder.setEngine(new Engine(2.5, 0)); builder.setTransmission(Transmission.MANUAL); builder.setGPSNavigator(new GPSNavigator()); } }
-
使用静态内部类当做建造者,进行链式调用
Car 产品类构建,私有构造
public class Car { private CarType carType; private int seats; private Engine engine; private Transmission transmission; private TripComputer tripComputer; private GPSNavigator gpsNavigator; private double fuel = 0; private Car(CarBuilder builder){ this.carType = builder.carType; this.seats = builder.seats; this.engine = builder.engine; this.transmission = builder.transmission; this.tripComputer = builder.tripComputer; this.gpsNavigator = builder.gpsNavigator; this.fuel = builder.fuel; } public static class CarBuilder{ private CarType carType; private int seats; private Engine engine; private Transmission transmission; private TripComputer tripComputer; private GPSNavigator gpsNavigator; private double fuel = 0; public CarBuilder(CarType carType) { this.carType = carType; } public CarBuilder setSeats(int seats) { this.seats = seats; return this; } public CarBuilder setEngine(Engine engine) { this.engine = engine; return this; } public CarBuilder setTransmission(Transmission transmission) { this.transmission = transmission; return this; } public CarBuilder setTripComputer(TripComputer tripComputer) { this.tripComputer = tripComputer; return this; } public CarBuilder setGpsNavigator(GPSNavigator gpsNavigator) { this.gpsNavigator = gpsNavigator; return this; } public CarBuilder setFuel(double fuel) { this.fuel = fuel; return this; } public Car build(){ return new Car(this); } } }
链式调用,build返回car对象
Car car = new Car.CarBuilder(CarType.SUV) .setSeats(4) .setEngine(new Engine(8.0,7.8)) .setTransmission(Transmission.AUTOMATIC) .setTripComputer(new TripComputer()) .setGpsNavigator(new GPSNavigator()) .setFuel(8.0) .build();
我的理解:
建造者适合用来构建复杂对象,比如 Netty
public static void main(String[] args) throws InterruptedException {
// 创建BossGroup和WorkerGroup,它们都是EventLoopGroup的子类
// BossGroup负责接收客户端的连接
// WorkerGroup负责处理接收到的客户端数据
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建ServerBootstrap对象,它是服务器启动的辅助类
ServerBootstrap bootstrap = new ServerBootstrap();
// 设置两个EventLoopGroup
bootstrap.group(bossGroup, workerGroup)
// 设置服务器通道类型为NioServerSocketChannel
.channel(NioServerSocketChannel.class)
// 设置服务器通道的配置选项
.option(ChannelOption.SO_BACKLOG, 128)
// 设置客户端通道的配置选项
.childOption(ChannelOption.SO_KEEPALIVE, true)
// 设置通道处理器链(添加自定义的ChannelHandler)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 添加EchoServerHandler到ChannelPipeline
ch.pipeline().addLast(new EchoServerHandler());
}
});
// 绑定端口并同步等待成功,这里会阻塞直到服务器成功启动
ChannelFuture f = bootstrap.bind(PORT).sync();
// 监听服务器关闭的ChannelFuture,以便在服务器关闭后执行清理工作
f.channel().closeFuture().sync();
} finally {
// 优雅地关闭EventLoopGroups,释放所有资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
1.4、原型模式
原型模式 Prototype
1.4.1、浅克隆(Shallow Copy)
**定义:**浅克隆是指创建一个新对象,新对象的属性值与原始对象相同,但对于引用类型的属性,仅复制其引用,而不复制引用所指向的对象本身。换句话说,新旧对象的引用类型属性指向相同的内存地址。
特点:
- 基本类型属性:浅克隆会为基本类型(如整型、浮点型、字符型、布尔型等)和不可变对象(如String、枚举等)创建新的副本,新旧对象间这些属性的值互不影响。
- 引用类型属性:对于引用类型(如数组、集合、自定义类对象等),浅克隆仅复制引用(即内存地址),新旧对象的引用类型属性指向同一个对象。这意味着,当其中一个对象通过引用修改了所指向对象的状态时,会影响到另一个对象。
首先,看看深拷贝和浅拷贝。
1.4.2、深克隆(Deep Copy)
**定义:**深克隆不仅复制原始对象的所有基本类型属性和不可变对象属性,还会递归复制所有引用类型属性所指向的对象,直到所有的对象都被复制到新创建的对象图中。新旧对象的引用类型属性分别指向独立的、内容相同的对象。
特点:
- 基本类型属性:与浅克隆相同,深克隆也为基本类型和不可变对象创建新的副本。
- 引用类型属性:对于引用类型,深克隆不仅复制引用,还为引用所指向的对象创建独立的副本。这样,即使其中一个对象通过引用修改了所指向对象的状态,也不会影响到另一个对象。
实现方式:
- 序列化/反序列化:将对象序列化成字节流,然后再将其反序列化为新对象,实现深克隆。这种方式要求对象及其引用类型属性都要实现
Serializable
接口。 - 手动递归复制:遍历对象的所有属性,对基本类型和不可变对象直接复制值,对引用类型递归调用复制方法或使用构造函数创建新对象。
使用IO流进行序列化
public static void main(String[] args) throws Exception { Teacher original = new Teacher(7800.00, 20, new Address("中国北京", 666)); File file = new File("a.txt"); try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) { // 序列化对象 out.writeObject(original); // 清空文件,准备反序列化新的对象 file.delete(); file.createNewFile(); // 反序列化对象,实现深拷贝 Teacher deepCopy = (Teacher) in.readObject(); System.out.println("Original: " + original); System.out.println("Deep Copy: " + deepCopy); System.out.println("================修改之前================="); // 修改复制对象的内部对象属性 deepCopy.getAddress().setCode(999); System.out.println("Original: " + original); System.out.println("Deep Copy: " + deepCopy); } }
运行结果:
Original: Teacher{salary=7800.0, age=20, address=Address{detail='中国北京', code=666}} Deep Copy: Teacher{salary=7800.0, age=20, address=Address{detail='中国北京', code=666}} ================修改之前================= Original: Teacher{salary=7800.0, age=20, address=Address{detail='中国北京', code=666}} Deep Copy: Teacher{salary=7800.0, age=20, address=Address{detail='中国北京', code=999}}
1.4.3、原型定义
菜鸟教程:
意图:用原型实例指定创基对象的种类,并且通过拷贝这些原型创建新的对象
https://refactoringguru.cn/design-patterns/prototype 中解释
意图:复制已有的对象,而又无需使代码依赖它们所属的类
维基百科中解释:
用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。 原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。
原型模式的实现方式:
- 创建一个原型接口,定义用于复制对象的方法。
- 创建具体原型类,实现原型接口,并提供具体的复制逻辑。
- 在客户端代码中,通过调用原型对象的复制方法来创建新的对象。
我的理解:
- 原型模式,就是用来创建对象(不用手动new 对象),但是基于原有对象,进行copy,希望把原对象的属性(优秀基因)都克隆出来
1.5、单例模式
单例模式(Singleton Pattern)
1.5.1、单例定义
https://refactoringguru.cn/design-patterns/singleton 中
意图: 保证一个类只有一个实例,并提供一个访问该实例的全局节点
实现方式:
- 在类中添加一个私有静态成员变量用于保存单例实例。
- 声明一个公有静态构建方法用于获取单例实例。
- 在静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。
- 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。
- 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。
菜鸟教程:
创建自己的对象,确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类。
注意:
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
1.5.2、懒汉式-线程不安全
菜鸟教程:
**是否 Lazy 初始化:**是
**是否多线程安全:**否
UnSavetySingleton
/**
* 懒汉式,线程不安全
*/
public class UnSavetySingleton {
// 私有成员
private String name;
private Integer age;
// 私有构造
private UnSavetySingleton(){}
// 返回静态实例
private static UnSavetySingleton singleton;
// 全局返回静态实例
public static UnSavetySingleton getInstance(){
try {
Thread.sleep(10); // 模拟执行业务
} catch (InterruptedException e) {
e.printStackTrace();
}
if (singleton == null){
singleton = new UnSavetySingleton();
}
return singleton;
}
}
UnSavetySingletonTest 测试
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(
2,
4,
1000,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200)
);
for (int i = 0; i < 100; i++) {
executorService.submit(()->{
UnSavetySingleton singleton = UnSavetySingleton.getInstance();
singleton.setName("zhangsan");
singleton.setAge(14);
System.out.println("张三===>"+singleton);
});
executorService.submit(()->{
UnSavetySingleton singleton = UnSavetySingleton.getInstance();
singleton.setName("lisi");
singleton.setAge(44);
System.out.println("李四===>"+singleton);
});
}
executorService.shutdown();
}
测试结果:
张三===>{name=‘zhangsan’, age=14}
张三===>{name=‘zhangsan’, age=14}
李四===>{name=‘lisi’, age=44}
张三===>{name=‘lisi’, age=14}
李四===>{name=‘lisi’, age=14}
李四===>{name=‘zhangsan’, age=14}
张三===>{name=‘zhangsan’, age=14}
测试结果,显示,对象的属性错乱,线程不安全
1.5.3、懒汉式-线程安全
菜鸟教程:
**是否 Lazy 初始化:**是
**是否多线程安全:**是
进行加锁
// 全局返回静态实例
public static UnSavetySingleton getInstance(){
// 进行加锁
synchronized (UnSavetySingleton.class){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (singleton == null){
singleton = new UnSavetySingleton();
}
return singleton;
}
}
1.5.4、饿汉式
菜鸟教程:
**是否 Lazy 初始化:**否
**是否多线程安全:**是
随着类的加载,对象作为静态成员变量
public class SingleStarve {
// 定义对象本身
private static SingleStarve singleStarve = new SingleStarve();
// 私有构造
private SingleStarve() {
}
// 全局访问
public static SingleStarve getInstance() {
return singleStarve;
}
}
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(
2,
4,
1000,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200)
);
for (int i = 0; i < 100; i++) {
executorService.submit(()->{
SingleStarve instance = SingleStarve.getInstance();
String thread = Thread.currentThread().getName();
System.out.println(thread+"===>"+instance);
});
executorService.submit(()->{
SingleStarve instance = SingleStarve.getInstance();
String thread = Thread.currentThread().getName();
System.out.println(thread+"===>"+instance);
});
}
executorService.shutdown();
}
测试结果:
pool-1-thread-1===>com.create_design.singleton.starvesavety.SingleStarve@1ed7aefb pool-1-thread-2===>com.create_design.singleton.starvesavety.SingleStarve@1ed7aefb pool-1-thread-2===>com.create_design.singleton.starvesavety.SingleStarve@1ed7aefb pool-1-thread-1===>com.create_design.singleton.starvesavety.SingleStarve@1ed7aefb pool-1-thread-2===>com.create_design.singleton.starvesavety.SingleStarve@1ed7aefb
thread-1 和 thread-2 都指向同一个实例对象
1.5.5、双检锁|双重校验锁
菜鸟教程
**JDK 版本:**JDK1.5 起
**是否 Lazy 初始化:**是
**是否多线程安全:**是
public class DCheckSingleton {
private String gender;
private String name;
// 私有构造
private DCheckSingleton (){};
// 实例本身
private static volatile DCheckSingleton singleton;
// 全局访问
public static DCheckSingleton getInstance(){
if (singleton == null){
synchronized (DCheckSingleton.class){
if (singleton == null){
singleton = new DCheckSingleton();
}
}
}
return singleton;
}
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(
2,
4,
1000,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(200)
);
for (int i = 0; i < 100; i++) {
executorService.submit(()->{
DCheckSingleton instance = DCheckSingleton.getInstance();
instance.setName("张三");
instance.setGender("男");
String thread = Thread.currentThread().getName();
System.out.println(thread+"===>"+instance);
});
executorService.submit(()->{
DCheckSingleton instance = DCheckSingleton.getInstance();
instance.setName("翠花");
instance.setGender("女");
String thread = Thread.currentThread().getName();
System.out.println(thread+"===>"+instance);
});
}
executorService.shutdown();
}
测试结果:
pool-1-thread-1===>{gender='男', name='张三'} pool-1-thread-1===>{gender='男', name='张三'} pool-1-thread-1===>{gender='女', name='翠花'} pool-1-thread-1===>{gender='男', name='张三'} pool-1-thread-1===>{gender='女', name='翠花'} pool-1-thread-1===>{gender='男', name='张三'}
对象的属性,没有错乱,线程安全
1.5.6、登记式|静态内部类
菜鸟教程:
**是否 Lazy 初始化:**是
**是否多线程安全:**是
/**
* 单例模式之静态内部类
*/
public class StaticSingleton {
// 私有构造
private StaticSingleton(){};
// 使用静态内部类来返回当前实例
private static class SingletonHolder{
private static final StaticSingleton SINGLETON = new StaticSingleton();
}
// 全局访问节点
public static StaticSingleton getInstance(){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return SingletonHolder.SINGLETON;
}
private String name;
private String color;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String print() {
return "{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(
10,
20,
1000,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(800)
);
for (int i = 0; i < 200; i++) {
executorService.submit(()->{
StaticSingleton instance = StaticSingleton.getInstance();
instance.setName("捷安特SPEEDER");
instance.setColor("丹尼蓝");
String thread = Thread.currentThread().getName();
System.out.println(thread+"===>"+instance+"===>"+instance.print());
});
executorService.submit(()->{
StaticSingleton instance = StaticSingleton.getInstance();
instance.setName("小米su7");
instance.setColor("紫金");
String thread = Thread.currentThread().getName();
System.out.println(thread+"===>"+instance+"===>"+instance.print());
});
executorService.submit(()->{
StaticSingleton instance = StaticSingleton.getInstance();
instance.setName("极客007");
instance.setColor("灰色");
String thread = Thread.currentThread().getName();
System.out.println(thread+"===>"+instance+"===>"+instance.print());
});
executorService.submit(()->{
StaticSingleton instance = StaticSingleton.getInstance();
instance.setName("比亚迪dmi秦");
instance.setColor("白色");
String thread = Thread.currentThread().getName();
System.out.println(thread+"===>"+instance+"===>"+instance.print());
});
}
executorService.shutdown();
}
- 私有化构造函数:将Singleton类的构造函数设为私有,使得外部无法通过new关键字直接创建Singleton的实例,保证了单例性。
- 静态内部类:定义一个名为SingletonHolder的静态内部类,它包含一个Singleton类型的静态成员变量INSTANCE。由于静态内部类只会被加载一次(即首次访问Singleton.getInstance()时),且加载过程是线程安全的,因此INSTANCE只会被实例化一次,确保了单例的唯一性。
- 懒加载:SingletonHolder类及其内部的INSTANCE变量只有在调用Singleton.getInstance()方法时才会被加载和初始化。这种机制称为“懒加载”或“延迟初始化”,避免了类加载阶段就初始化单例对象,提高了系统资源利用率。
- 线程安全:由于Java虚拟机在类加载时会保证线程安全,所以即使在多线程环境下,SingletonHolder的初始化也是线程安全的,无需额外的同步代码,简化了实现。
综上所述,使用静态内部类实现单例模式,既能保证单例的唯一性,又能实现延迟加载,同时具备线程安全性,是一种高效、简洁的实现方式。
-
实例的成员变量,如果没有加任何锁,是线程不安全的,但是当前的实例是安全,都指向同一个对象
1.5.7、枚举
菜鸟教程
**JDK 版本:**JDK1.5 起
**是否 Lazy 初始化:**否
**是否多线程安全:**是
/**
* 单例模式之枚举方式
*/
public enum EnumSingleton {
INSTANCE;
private String name;
private String color;
public String whateverMethod(){
return this+"===>"+this.print();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String print() {
return "{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
public static void main(String[] args) {
ExecutorService executorService = new ThreadPoolExecutor(
10,
20,
1000,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(800)
);
for (int i = 0; i < 200; i++) {
executorService.submit(()->{
String thread = Thread.currentThread().getName();
EnumSingleton instance = EnumSingleton.INSTANCE;
instance.setName("捷安特SPEEDER");
instance.setColor("丹尼蓝");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread+"===>"+instance.whateverMethod());
});
executorService.submit(()->{
EnumSingleton instance = EnumSingleton.INSTANCE;
instance.setName("小米su7");
instance.setColor("紫金");
String thread = Thread.currentThread().getName();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread+"===>"+instance.whateverMethod());
});
executorService.submit(()->{
EnumSingleton instance = EnumSingleton.INSTANCE;
instance.setName("极客007");
instance.setColor("灰色");
String thread = Thread.currentThread().getName();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread+"===>"+instance.whateverMethod());
});
executorService.submit(()->{
EnumSingleton instance = EnumSingleton.INSTANCE;
instance.setName("比亚迪dmi秦");
instance.setColor("白色");
String thread = Thread.currentThread().getName();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread+"===>"+instance.whateverMethod());
});
}
executorService.shutdown();
}
测试结果:
- 实例的成员变量,如果没有加任何锁,是线程不安全的,但是当前的实例是安全,都指向同一个对象
我的理解:
- 单例模式,只想始终都操作一个对象,如果单例还有其他的成员属性,如果是只读的话,不会有线程安全问题,否则需要考虑
- 如果需要对单例的成员属性,进行修改,双检锁可以保证线程安全
菜鸟教程:
**经验之谈:**一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。
1.6、小结
- 工厂方法
把对象的创建,交给工厂去完成。
- 抽象工厂
产品等级:
同一类型的产品,比如手机,老年机、智能机
产品族:
不是同一类产品,比如电脑、电视、冰箱、空调
抽象工厂下有一系列对应的工厂,生产同一类型的产品。
- 建造者
建造者,适合构建复杂的对象,通过一步一步的build,给引用的对象进行实例化。
- 原型
把当前对象进行copy,理解浅拷贝和深拷贝。
浅拷贝,只拷贝基本数据类型,引用数据类型是直接复用
深拷贝,拷贝对象所有的属性,包括引用数据类型。
- 单例
只期望永远使用一个对象,外部访问,只能通过单例本身暴露的接口获取对象,不能在外部new 出来单例。
2、结构型模式(7种)
2.1、适配器
2.1.1、定义
适配器模式 Adapter
菜鸟教程:
两个不兼容的接口之间的桥梁
- 读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡
https://refactoringguru.cn/design-patterns/adapter
意图:
适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。
通义千问:
适配器模式(Adapter Pattern)是一种结构型设计模式,它的主要目的是在不修改原有接口的前提下,使原本因接口不兼容而无法一起工作的类能够协同工作。适配器模式通过创建一个适配器类,将一个接口转换成另一个接口,使得客户程序可以透明地使用新接口调用旧接口的功能。适配器模式的核心思想是“封装差异”,它提供了将一个类的接口转换为客户期望的另一个接口的能力,使原本接口不兼容的类可以一起工作。
- 提高代码复用:无需修改原有代码,即可使不兼容的接口协同工作。
- 增加系统的灵活性:通过添加新的适配器,可以轻松地将新类或第三方组件集成到现有系统中。
- 减少系统间的耦合:客户端代码面向目标接口编程,无需了解具体的被适配者实现细节。
适配器模式常用于系统集成、框架扩展、遗留系统改造等场景,有助于解决因接口不匹配带来的兼容性问题,提升系统的可扩展性和可维护性。
结构
1. 被适配者(Adaptee): 这是已有的、具有特定功能但是接口不符合客户端需求的类或接口
2. 目标接口(Target): 这是客户端期望的接口,定义客户端希望与之交互的方法
3. 适配器(Adapter) : 适配器实现了目标接口,并持有对适配者的引用。适配器内部通过调用被适配者的相关方法来实现目标接口的方法,从而将被适配者的接口转换为客户端
可以接受的形式。
2.1.2、类适配器
-
实现方式:通过继承被适配类(Adaptee)来实现目标接口(Target)。适配器(Adapter)作为子类,继承了被适配类的全部功能,并在适配器类中提供目标接口所需的方法。
-
特点
- 使用了类的继承关系,适配器直接是被适配类的子类。
- 如果被适配类是final的或者不支持继承,类适配器模式就无法使用。
- 耦合度相对较高,因为适配器直接依赖于被适配类的具体实现。
适配器类通常采用如下方式实现:
- 继承:适配器类直接继承现有的适配者类(Adaptee),这样适配器就继承了适配者的所有属性和行为。
- 实现目标接口:适配器类同时还实现目标接口(Target),这意味着它必须提供目标接口所定义的所有方法。
假定,原程序计算图形的周长,直接 (长 + 宽 ) x 2 。现在需要适配三角形、圆形的周长计算
public interface Target {
double calcPerimeter(ShapeEnum type, BaseShape shape);
}
public class ConcreteTarget implements Target {
@Override
public double calcPerimeter(ShapeEnum type, BaseShape shape) {
switch (type){
case SQUARE: // 正方形
Square square = (Square) shape;
return Math.pow(square.getX(),2);
case RECTANGLE: // 长方形
Rectangle rectangle = (Rectangle) shape;
return rectangle.getY() * rectangle.getX();
}
return 0.0;
}
}
public class Adaptee {
public double specificRequest(ShapeEnum type, BaseShape shape) {
switch (type){
case CIRCLE: // 圆形
Circle circle = (Circle) shape;
return circle.getR()*2*3.14159;
case TRIANGLE: // 三角形
Triangle triangle = (Triangle) shape;
return triangle.getX()+triangle.getY()+triangle.getZ();
}
return 0.0;
}
}
public class Adapter extends Adaptee implements Target {
@Override
public double calcPerimeter(ShapeEnum type, BaseShape shape) {
return super.specificRequest(type,shape);
}
}
2.1.3、对象适配器
-
实现方式:通过持有被适配类的一个实例(通常通过构造函数注入),并在适配器类中实现目标接口。适配器通过调用被适配类实例的方法来完成目标接口的实现。
-
特点
- 使用了对象组合(Has-a)关系,适配器内部包含一个被适配类的实例。
- 更加灵活,可以适配多个不同的被适配类,只需在适配器中替换被适配类实例即可。
- 耦合度较低,适配器与被适配类之间的关联更为松散,仅依赖于被适配类的公开接口。
构造函数注入
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public double calcPerimeter(ShapeEnum type, BaseShape shape) {
return adaptee.specificRequest(type,shape);
}
}
2.1.4、接口适配器
- 实现方式:当一个接口中有较多方法,而客户端只需要使用其中一部分时,可以创建一个抽象类实现该接口,并为接口中的每一个方法提供一个默认空实现(do nothing)。这样,客户端可以继承这个抽象类,仅需重写实际需要的方法,避免了实现接口时必须实现所有方法的问题。
- 特点
- 主要针对接口设计,而非两个已有类之间的适配。
- 提供了一个扩展点,使得客户端可以选择性地实现接口中的方法。
通义千问:
// 接口,包含多个方法 public interface AdvancedInterface { void method1(); void method2(); void method3(); // ... 更多方法 } // 抽象适配器,实现接口并提供默认空实现 public abstract class AbstractAdapter implements AdvancedInterface { @Override public void method1() { // 默认空实现 } @Override public void method2() { // 默认空实现 } @Override public void method3() { // 默认空实现 } // ... 对接口中所有方法提供默认空实现 } // 具体适配器,仅实现关心的方法 public class ConcreteAdapter extends AbstractAdapter { @Override public void method2() { // 实现 method2 的具体逻辑 } }
我的理解:
适配器模式,一般都是已经有的程序代码,或者第三方服务,不满足新需求,或者需要兼容之前的版本。
-
三角插头,通过适配器,适配不同的插座
-
java代码,通过jvm,来适配不同的系统环境
-
高压电,通过变压器,输送到居民220V,工厂380V
-
适配多语言环境
目标接口(Target): 定义一个通用的LanguageResourceAdapter接口(或抽象类),它声明了应用程序需要的所有语言资源获取方法,如getString(key)、getDateFormat(pattern)等。这个接口代表了客户端(如视图层、业务逻辑层)期望的统一语言资源访问方式。 被适配者(Adaptees): 每种语言资源的具体来源(如JSON文件、XML文件、数据库表、REST API等)都可以视为被适配者。它们可能有不同的访问方式、数据结构和API,但都包含了应用程序需要的语言资源。 适配器(Adapters): 创建一系列适配器类,如JsonFileLanguageResourceAdapter、XmlFileLanguageResourceAdapter、DatabaseLanguageResourceAdapter、RestApiLanguageResourceAdapter等。每个适配器类都实现了LanguageResourceAdapter接口,并在其内部封装了对特定语言资源源的访问逻辑。适配器将源接口转换为目标接口,使得无论资源来自何处,客户端都能通过统一的接口进行访问。 客户端(Client): 应用程序的其他部分(如视图控制器、服务类等)作为客户端,它们依赖于LanguageResourceAdapter接口而非具体的适配器实现。当用户切换语言设置时,根据配置动态创建或切换到相应的适配器实例,然后通过目标接口方法获取所需的语言资源。
所以,使用适配器,应该是,当前的产品,通过中间的适配器,让产品能在不同的环境中正常使用,被兼容。
2.2、装饰
装饰模式 Decorator
2.2.1、定义
菜鸟教程:https://www.runoob.com/design-pattern/decorator-pattern.html
允许向一个现有的对象添加新的功能,同时又不改变其结构
装饰器模式通过将对象包装在装饰器类中,以便动态地修改其行为
意图:
动态的给一个类添加一些额外的职责,在不想增加子类的情况下扩展类
https://refactoringguru.cn/design-patterns/decorator
意图:装饰模式是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中,为原来的对象绑定新的行为
通义千问:
装饰模式是一种结构型设计模式,它运行在运行时间动态地向一个对象添加新的行为或责任,同时保持队形的类结构不变。 装饰模式,通过创建一个装饰器类来包裹(即装饰)原始对象,并在装饰器中提供额外工作或修改原始对象的行为,从而达到扩展功能的目的。 这种模式强调的是在不改变对象接口的前提下,通过组合而非继承的方式增加对象的功能,从而提供更大的灵活性。
装饰模式的关键组件和工作原理:
- 组件接口 (Component interface) : 定义了基础对象(被装饰对象)所具有的公共接口,它描述了对象的基本行为。所有具体组件和装饰器都都要实现此接口,确保在外部看来,它们具有相同的接口。
- 具体组件(Concrete Component): 实现了组件接口的最基本对象,它代表了需要装饰的对象
- 装饰器(Decorator):装饰器实现了接口组件,同时持有一个组件接口的引用。装饰器可以在实现组件接口定义的方法时,选择调用被装饰对象(即持有的组件接口引用)对应的方法,也可以在调用前后添加额外的操作,从而增强或修改被装饰对象的行为。装饰器通常持有一个构造函数,用与接收被装饰的具体组件。
- 具体装饰器 (Concete Decorators) : 提供了具体的行为增强或修改。每个具体装饰器都继承自装饰器类,并在需要的地方重写组件接口方法,以实现特定的装饰效果。一个具体的装饰器可以装饰另一个装饰器,从而形成装饰器链,实现多层装饰。
- 使用场景:
当需要为已经存在的对象添加新功能,但又不希望修改其源代码或继承其类时。
当需要为不同类型的对象提供统一的接口,且希望在不改变这些对象本身的情况下,为这些对象增加额外职责时。
当需要在运行时动态地添加、删除或切换对象的责任时。
- 优点:
扩展性强:通过组合而非继承的方式扩展对象功能,避免了类层次结构的过深和过复杂。
符合开闭原则:在不修改原有类的情况下,通过添加新的装饰器类就能扩展功能,对扩展开放,对修改关闭。
灵活性高:装饰器可以在运行时动态地添加或移除,使得系统更易于配置和使用。
透明性:装饰后的对象与原对象在对外接口上保持一致,客户端无需关心是否使用了装饰器
上图,对基础的咖啡售卖进行增强,牛奶咖啡,价格添加 0.1 的成本;加糖咖啡,价格添加 0.05 的成本。
2.2.2、举例
一个计算两个数字的 + - * / 的工具类,使用装饰器模式,让计算结果保留6位小数。
-
组件接口
public interface ICalc { // + double add(double num1,double num2); // - double substract(double num1,double num2); // * double mul(double num1,double num2); // / double divied(double num1,double num2); }
-
具体组件
public class CalcComponent implements ICalc { @Override public double add(double num1, double num2) { return num1 + num2; } @Override public double substract(double num1, double num2) { return num1 - num2; } @Override public double mul(double num1, double num2) { return num1 - num2; } @Override public double divied(double num1, double num2) { return num1 / num2; } }
-
装饰器
/** * 抽象装饰器 */ public abstract class CalcDecorator implements ICalc { // 定义原始对象 private ICalc calc; public CalcDecorator(ICalc calcComponent) { this.calc = calcComponent; } @Override public double add(double num1, double num2) { return calc.add(num1,num2); } @Override public double substract(double num1, double num2) { return calc.substract(num1,num2); } @Override public double mul(double num1, double num2) { return calc.mul(num1,num2); } @Override public double divied(double num1, double num2) { return calc.divied(num1,num2); } }
-
具体装饰器
public class DecimalCalcDecorator extends CalcDecorator{ private static final BigDecimal ONE = new BigDecimal(1); public DecimalCalcDecorator(ICalc calcComponent) { super(calcComponent); } @Override public double add(double num1, double num2) { return new BigDecimal(super.add(num1, num2)).divide(ONE,6, RoundingMode.HALF_DOWN).doubleValue(); } @Override public double substract(double num1, double num2) { return new BigDecimal(super.substract(num1, num2)).divide(ONE,6, RoundingMode.HALF_DOWN).doubleValue(); } @Override public double mul(double num1, double num2) { return new BigDecimal(super.mul(num1, num2)).divide(ONE,6, RoundingMode.HALF_DOWN).doubleValue(); } @Override public double divied(double num1, double num2) { return new BigDecimal(super.divied(num1, num2)).divide(ONE,6, RoundingMode.HALF_DOWN).doubleValue(); } }
-
test
public static void main(String[] args) { ICalc calc = new CalcComponent(); double add = calc.add(0.001, 0.2002); System.out.println("add = " + add); double substract = calc.substract(55.2, 0.54411); System.out.println("substract = " + substract); double mul = calc.mul(4.156, 0.3); System.out.println("mul = " + mul); double divied = calc.divied(0.81, 0.33); System.out.println("divied = " + divied); System.out.println("\n===============================>\n"); DecimalCalcDecorator dcd = new DecimalCalcDecorator(calc); add = dcd.add(0.001, 0.2002); System.out.println("add = " + add); substract = dcd.substract(55.2, 0.54411); System.out.println("substract = " + substract); mul = dcd.mul(4.156, 0.3); System.out.println("mul = " + mul); divied = dcd.divied(0.81, 0.33); System.out.println("divied = " + divied); }
我的理解
装饰器模式,对现有类或接口进行增强,但是不使用继承的方式,把需要增强的对象,以构造注入的方式,然后在具体的装饰器里,增强构造注入对象的方法。
相当于基本数据类型的包装类,基本数据类没有可以调用的方法函数,但是使用包装类就有很多AP
2.3、代理
代理模式 Proxy
2.3.1、定义
菜鸟教程:https://www.runoob.com/design-pattern/proxy-pattern.html
在代理模式中,一个类代表另一个类的功能
意图:为其他对象提供一种代理以控制对这个对象的访问
https://refactoringguru.cn/design-patterns/proxy
意图:代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。
主要涉及到以下几个核心角色:
- 抽象主题(Subject):
- 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。
- 真实主题(Real Subject):
- 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问。
- 代理(Proxy):
- 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等。
- 客户端(Client):
- 使用抽象主题接口来操作真实主题或代理主题,不需要知道具体是哪一个实现类
2.3.2、静态代理
静态代理在编译时就已经确定代理类及其代理行为,代理类和目标类都是在编译期就生成了实体类,代理类通常是通过手工编写或者工具生成的。
特点:
- 静态代理需要为每个需要被代理的类创建一个对应的代理类,代理类和目标类通常会实现同一个接口或继承自同一个父类。
- 代理类和目标类的关系在编译阶段就已经明确,运行时不能改变。
优缺点:
- 优点是代理类的逻辑在编译时就可以验证,错误容易发现。
- 缺点是如果有很多类需要被代理,会产生大量的代理类,且每次增加新的业务功能时,都需要手动编写新的代理类,不够灵活。
-
抽象主题
public interface IcalcInterface { double add(double num1,double num2); }
-
真实主题
public class CalcAdd implements IcalcInterface { @Override public double add(double num1, double num2) { return num1+num2; } }
-
代理
public class AddProxy implements IcalcInterface { private CalcAdd calcAdd; public AddProxy(){ this.calcAdd = new CalcAdd(); } @Override public double add(double num1, double num2) { // 一些其他前置操作 System.out.println("一些其他前置操作"); double res = calcAdd.add(num1, num2); System.out.println("计算结果是:"+res); // 一些其他后置操作 System.out.println("一些其他后置操作"); return res; } }
-
客户端
public static void main(String[] args) { IcalcInterface calc = new AddProxy(); calc.add(1, 2); }
2.3.4、动态代理
动态代理则是指在运行时动态创建代理类和代理对象,Java中有两种实现方式:
- JDK动态代理:基于Java反射机制实现,需要被代理的类必须实现一个或多个接口。代理类是由JVM在运行时动态生成的,它实现了目标类实现的所有接口,并在方法调用前后添加额外的操作。
- CGLIB动态代理:CGLIB库通过生成目标类的子类来实现代理,不需要目标类实现接口。CGLIB通过字节码技术为类创建子类,并在子类中加入拦截方法,从而实现动态代理。
特点:
- 动态代理类是在运行时动态生成的,更加灵活,可以根据需要随时生成不同代理类。
- JDK动态代理只能代理实现了接口的类,而CGLIB可以代理没有实现接口的类。
优缺点:
- 优点是相比于静态代理,动态代理可以更灵活地为多种类提供代理,无需为每一种类都创建代理类,代码更简洁,扩展性更好。
- 缺点是动态代理的性能略低于静态代理,因为涉及到反射操作,另外CGLIB代理由于使用了字节码操作,增加了调试难度。
在Spring框架中,AOP(面向切面编程)就广泛使用了动态代理技术,通过代理模式在目标方法执行前后添加横切逻辑,如事务管理、日志记录等。
2.3.4.1、jdk动态代理
只能代理接口
@Component
public class UserProxy {
public UserService getUserService(UserService userService){
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
// 计算耗时
long start = System.nanoTime();
String str = "";
for (int i = 0; i < 1000; i++) {
str +=i;
}
Object obj = method.invoke(userService, args);
long end = System.nanoTime();
System.out.println(name + "总共耗时=" + (end - start) + "纳秒");
return obj;
}
});
return userServiceProxy;
}
}
参考博客:https://blog.csdn.net/weixin_43973404/article/details/113094312
2.3.4.2、CGLIB动态代理
// 使用 spring 框架提供的 Cglib进行动态代理
@Test
void testCglib(){
// Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置父类类型
enhancer.setSuperclass(userService.getClass());
// 设置回调方法
enhancer.setCallback(callback);
// 增强逻辑处理
Callback callback = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name = method.getName();
// 计算耗时
long start = System.nanoTime();
String str = "";
for (int i = 0; i < 1000; i++) {
str +=i;
}
Object obj = method.invoke(userService, args);
long end = System.nanoTime();
System.out.println(name + "总共耗时=" + (end - start) + "纳秒");
return obj;
}
};
// 获取代理对象
UserService userServiceInstance = (UserService) enhancer.create();
// 代理对象执行方法
userServiceInstance.selectAll();
}
参考博客:https://blog.csdn.net/qq_25827845/article/details/87513102
我的理解:
代理模式,就是代替一个类,在调用类的方法是,进行一些操作,比如AOP切面,比如Spring的事务管理,比如使用注解@log进行日志记录,都是使用代理对象进行管理
2.4、外观
外观模式 Facade
2.4.1、定义:
菜鸟教程:
**意图:**为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
https://refactoringguru.cn/design-patterns/facade/java/example
意图:外观模式是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。
通义千问:
定义:外观模式定义了一个高层接口,该接口使得子系统更加容易使用。它封装了子系统内部的复杂性,为外部用户提供了一个简单、一致且更易理解的接口,使得用户无需了解子系统的内部组件以及它们之间的交互方式。
目的:
- 简化接口:通过提供一个简化的接口,屏蔽子系统的复杂性,减少客户端直接与子系统内多个组件交互的负担。
- 解耦:降低客户端与子系统间的耦合度,子系统内部的变化不会直接影响到客户端,反之亦然。
- 模块化:将复杂的子系统组织成更易于管理的模块,便于分阶段开发和独立维护。
- 隐藏实现细节:对外部隐藏子系统的具体实现细节和内部结构,保护子系统的封装性。
- 简化使用:对于新用户或偶尔使用的用户,提供一个简单的接入点,避免他们直接面对复杂的子系统。
结构与角色
外观模式涉及到以下角色:
- Facade(外观):定义了一个高层接口,它包含了对各个子系统接口的引用,负责与各个子系统进行交互,并向客户端提供易于使用的接口。
- Subsystems(子系统):由多个相关的类或模块组成,它们各自提供了特定的功能,构成了系统的复杂部分。子系统并不知道 Facade 的存在,它们通常会提供更细化的接口供 Facade 使用。
- Client(客户端):通过与 Facade 进行交互来使用子系统功能,无需直接与子系统内部的组件打交道。
工作原理
- 客户端通过调用 外观 提供的接口来请求服务。
- 外观 接收到请求后,协调、调度或直接调用相应的 子系统 组件来完成请求。
- 子系统 执行具体任务,并将结果返回给 外观(如有必要)。
- 外观 可能会对子系统返回的结果进行进一步处理,然后将最终结果返回给 客户端
// 子系统类 class SubsystemA { public void operationA1() { /* ... */ } public void operationA2() { /* ... */ } } class SubsystemB { public void operationB1() { /* ... */ } public void operationB2() { /* ... */ } } // 外观类 class Facade { private final SubsystemA subsystemA = new SubsystemA(); private final SubsystemB subsystemB = new SubsystemB(); public void complexOperation() { subsystemA.operationA1(); subsystemB.operationB1(); subsystemA.operationA2(); } } // 客户端代码 public class Client { public static void main(String[] args) { Facade facade = new Facade(); facade.complexOperation(); // 客户端只需调用外观类的简单方法 } }
参考博客:https://blog.csdn.net/weixin_43521890/article/details/123992979
我的理解:
外观设计模式,就是集成,把多个杂碎进行统一管理,客户端不需要一个一个处理,交给一个中间层进行管理。
public class TurnLeft {
public void left(double l){
System.out.println("向左转"+l+"米");
}
}
public class Gostight {
public void go(double g){
System.out.println("直行"+g+"米");
}
}
public class TurnRight {
public void right(double r){
System.out.println("向右转"+r+"米");
}
}
public class Arrive {
public void arrive(){
System.out.println("到达目的地了。");
}
}
public class LeadFacde {
private Gostight gostight;
private TurnLeft turnLeft;
private TurnRight turnRight;
private Arrive arrive;
public LeadFacde(){
gostight = new Gostight();
turnLeft = new TurnLeft();
turnRight = new TurnRight();
arrive = new Arrive();
}
public void type1(){
turnLeft.left(300);
gostight.go(500);
turnRight.right(100);
arrive.arrive();
}
public void type2(){
gostight.go(500);
turnLeft.left(300);
turnRight.right(100);
arrive.arrive();
}
}
public static void main(String[] args) {
/*
左转300米
直行500米
右转100米
就到了
*/
TurnLeft turnLeft = new TurnLeft();
turnLeft.left(300);
Gostight gostight = new Gostight();
gostight.go(500);
TurnRight turnRight = new TurnRight();
turnRight.right(100);
Arrive arrive = new Arrive();
arrive.arrive();
System.out.println("============Facde=================");
LeadFacde leadFacde = new LeadFacde();
leadFacde.type1();
}
2.5、桥接
桥接模式 Bridge
2.5.1、定义
菜鸟教程
意图:将抽象部分与实现部分分离,使他们都可以独立的变化【解耦】
使用场景:
- 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使他们在抽象层建立一个关联的联系
- 对于那些不希望使用继承或因为多层继承导致系统类个数急剧增加的系统,桥接模式尤为适用
- 一个类存在两个维度的变化,使用桥接模式再合适不过
桥接模式几个关键的角色:
- 抽象(Abstraction): 定义抽象接口,通常包含对实现接口的引用
- 扩展抽象(Refined Abstraction):对抽象的扩展,可以是抽象类的子类或具体的实现类
- 实现 (Implementor) : 定义实现接口,提供基本操作的接口
- 具体实现(Conrete Implementor): 实现接口的具体类
可以看到,Shape通过成员变量引用了DrawApi,所以Shape 可以通过这个引用绘制不同颜色的圆
https://refactoringguru.cn/design-patterns/bridge
意图:桥接模式是一种结构型设计模式,可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构
实现方式:
- 明确类中独立的维度。 独立的概念可能是: 抽象/平台, 域/基础设施, 前端/后端或接口/实现。
- 了解客户端的业务需求, 并在抽象基类中定义它们。
- 确定在所有平台上都可执行的业务。 并在通用实现接口中声明抽象部分所需的业务。
- 为你域内的所有平台创建实现类, 但需确保它们遵循实现部分的接口。
- 在抽象类中添加指向实现类型的引用成员变量。 抽象部分会将大部分工作委派给该成员变量所指向的实现对象。
- 如果你的高层逻辑有多个变体, 则可通过扩展抽象基类为每个变体创建一个精确抽象。
- 客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。 此后, 客户端只需与抽象对象进行交互, 无需和实现对象打交道。
https://cloud.tencent.com/developer/article/2183989
桥接模式(Bridge Pattern)也称为桥梁模式、接口模式或者柄体模式,有点像适配器模式,也是 GoF 的 23 种设计模式中的一种结构型设计模式。 桥接模式 是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。
关于桥接模式的应用场景
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。桥接模式适用于以下几种业务场景:
- 在抽象和具体实现之间需要增加更多的灵活性的场景
- 一个类存在两个或多个独立变化的维度,而这两个或多个维度都需要独立进行扩展
- 不希望使用继承,或因为多层继承导致系统类的个数剧增
2.5.2、举例
自行车
- 自行车类型
- 城市交通出行,普通自行车
- 山地越野,山地自行车
- 极速竞技,公路自行车
- 自行车变速器
- 没有变速器
- 蓝图变速器
- 禧玛诺变速器
- 自行车速断
- 15km
- 21km
- 42km
public abstract class Bicycle { protected ISpeed iSpeed; protected IContructure iContructure; protected ITransmission transmission; public Bicycle(ISpeed iSpeed, IContructure iContructure, ITransmission transmission) { this.iSpeed = iSpeed; this.iContructure = iContructure; this.transmission = transmission; } public abstract void run(); } public class NormalCycle extends Bicycle { private String name; public NormalCycle(ISpeed iSpeed, IContructure iContructure, ITransmission transmission, String name) { super(iSpeed, iContructure, transmission); this.name = name; } @Override public void run() { System.out.println(name + "自行车,搭配 " + iContructure.trun() + transmission.getTransmiss() + "变速器,以" + iSpeed.speed() + "Km/h的速度休闲骑"); } }
我的理解
在抽象层,进行以成员变量的形式引用第三方的接口/抽象,把第三方通过构造方式注入,让后代继承该抽象层可以使用或操作第三方的方法。所以,桥接模式,是抽象层,成员变量的引用。
2.6、组合
组合模式 Composite
2.6.1、定义
菜鸟教程:
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
**关键代码:**树枝内部组合该接口,并且含有内部属性 List,里面放 Component。
- 组件 Component
- 定义了组合中所有对象的通用接口,可以是抽象类或者接口。它申明了用于访问和管理子组件的方法,包括添加、删除、获取子组件
- 叶子结点leaf
- 表示组合中的叶子节点,叶子节点没有子节点。它实现了组件接口的方法,但通常不包含子组件
- 复合节点/容器节点 Composite
- 表示组合中的复合对象,复合对象可以包含子节点,也可以是其他复合节点,它实现了组件接口的方法,包括管理子组件的方法
重构大师
https://refactoringguru.cn/design-patterns/composite
意图:组合模式是一种结构型设计模式,你可以使用它将对象哦组合成树状结构,并且能像使用独立对象一样使用它们
通义千问
组合设计模式(Composite Pattern)是一种结构型设计模式,它主要用于处理对象之间的“部分-整体”关系,使得单个对象(叶子节点)和对象组合(容器节点)在客户端代码中可以以一致的方式进行处理。组合模式的核心思想是将对象组织成树形结构,使得用户能够以统一的方式来对待单个对象和对象组合。
组合模式的基本结构:
- Component(组件): 这是组合中的基本接口或抽象类,定义了所有组件(包括叶子节点和容器节点)共有的方法,如添加子组件、删除子组件、获取子组件等。这些方法在叶子节点中可能没有实际意义,但在容器节点中会被实现。
- Leaf(叶子节点): 叶子节点是组合结构中的终端节点,不包含任何子节点。它实现了Component接口,但对于那些与子节点相关的操作(如添加、删除子节点),通常提供空实现或抛出异常。
- Composite(容器节点): 容器节点是包含零个或多个子组件的复合对象,它实现了Component接口,并提供了用于管理子组件的方法(如添加、删除和遍历子节点)。容器节点可以包含叶子节点或其他容器节点,形成了一个树形结构。
组合模式的主要优点:
- 一致性: 客户端可以以统一的方式处理单个对象和对象组合,无需关心处理的对象是简单还是复杂的。
- 透明性: 用户无需关心操作的具体对象是叶子节点还是容器节点,可以像操作单个对象一样操作整个对象组合。
- 易于扩展和维护: 新增或修改组件类型时,只需关注新组件是否符合Component接口,客户端代码通常无需改动。
组合模式的应用场景:
- 树形结构表示: 如文件系统(文件和目录)、组织结构(员工和部门)、UI组件(基础控件和容器控件)等。
- 操作的统一处理: 当需要对一组对象进行相同的操作时,如计算组件的总成本、渲染组件的图形表示、遍历组件树以进行搜索或更新等。
- 动态责任分配: 当责任需要在运行时动态分配到单个对象或对象组合时,组合模式可以简化这种责任分配和管理。
// Component接口定义了所有组件共有的方法 public interface Component { void add(Component child); void remove(Component child); void operation(); } // Leaf类实现了Component接口,但其add和remove方法为空实现或抛异常 public class Leaf implements Component { @Override public void add(Component child) { throw new UnsupportedOperationException("Leaf nodes cannot have children."); } @Override public void remove(Component child) { throw new UnsupportedOperationException("Leaf nodes cannot have children."); } @Override public void operation() { System.out.println("Leaf node operation."); } } // Composite类实现了Component接口,并提供了管理子组件的方法 public class Composite implements Component { private List<Component> children = new ArrayList<>(); @Override public void add(Component child) { children.add(child); } @Override public void remove(Component child) { children.remove(child); } @Override public void operation() { for (Component child : children) { child.operation(); } System.out.println("Composite node operation."); } } // 客户端代码可以一致地处理单个对象和对象组合 public class Client { public static void main(String[] args) { Component leaf1 = new Leaf(); Component leaf2 = new Leaf(); Component composite = new Composite(); composite.add(leaf1); composite.add(leaf2); composite.operation(); // 一致地处理组合对象 } }
https://segmentfault.com/a/1190000040870242
2.6.2、举例
public abstract class EmpComponent {
protected String name;
protected double salary;
public EmpComponent(String name, double salary) {
this.name = name;
this.salary = salary;
}
// 添加
public abstract int add(EmpComponent e);
// 删除
public abstract int remove(EmpComponent e);
// 展示
public abstract void show();
}
public class EmpComsite extends EmpComponent{
private final List<EmpComponent> EMP_LIST;
public EmpComsite(String name, double salary) {
super(name, salary);
EMP_LIST = new ArrayList<>();
}
@Override
public int add(EmpComponent e) {
if (EMP_LIST == null){
return -1;
}
EMP_LIST.add(e);
return 1;
}
@Override
public int remove(EmpComponent e) {
if (EMP_LIST == null){
return -1;
}
EMP_LIST.remove(e);
return 1;
}
@Override
public void show() {
if (EMP_LIST == null){
System.out.println("空空如也!");
return;
}
System.out.println("名字:"+name+",薪资:"+salary);
EMP_LIST.forEach(EmpComponent::show);
}
}
public static void main(String[] args) {
EmpComponent leaf1 = new EmpComsite("张三",1000);
EmpComponent leaf2 = new EmpComsite("李四",2000);
EmpComponent leaf3 = new EmpComsite("王五",3000);
EmpComponent leaf4 = new EmpComsite("赵六",4000);
EmpComponent comsite1 = new EmpComsite("李白",5000);
EmpComponent comsite2 = new EmpComsite("刘邦",6000);
comsite1.add(leaf1);
comsite1.add(leaf2);
comsite2.add(leaf3);
comsite2.add(leaf4);
comsite2.add(comsite1);
comsite2.show();
System.out.println("======================================>");
comsite2.remove(comsite1);
comsite2.show();
}
我的理解
组合模式,觉得有点像单链表,单链表是有一个引用指向下一个对象,而组合模式是本身有一个集合容易,存储下一个,哦不是,是下一群。所以组合模式可以使用递归遍历。
2.7、享元
享元模式 Flyweight
2.7.1、定义
菜鸟教程:
享元设计模式主要用于减少创建对象的数量,以减少内存占用和提高性能。尝试进行试用现有的同类对象,如果找不到匹配的对象,则创建新对象。
意图:运用共享技术有效地支持大量细粒度的对象
主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
享元模式几个核心角色:
- 享元工厂 Flyweight Factory
- 负责创建和管理享元对象,通常包含一个池(缓存)用于存储和复用已经创建的享元对象
- 抽象享元 Flyweight
- 定义具体享元和非共享享元的接口,通常包含了设置外部状态的方法
- 具体享元 Concrete Flyweight
- 实现了抽象享元的接口,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态则由客户端传递
- 客户端 Client
- 使用享元工厂获取享元对象,并通过设置外部状态来操作享元对象。客户端通常不需要关心享元对象的具体实现
https://refactoringguru.cn/design-patterns/flyweight
意图:享元模式是一种结构型设计模式,它摈弃了在每个对象中保存所有数据的方式,通过共享多个对象共有的相同状态,让你能够在有限的内存中加载更多的对象
使用示例: 享元模式只有一个目的: 将内存消耗最小化。 如果你的程序没有遇到内存容量不足的问题, 则可以暂时忽略该模式。
识别方法: 享元可以通过构建方法来识别, 它会返回缓存对象而不是创建新的对象。
通义千问:
享元(Flyweight)设计模式是一种用于性能优化的结构型设计模式,其主要目的是通过共享大量细粒度对象来减少系统中的对象数量,从而降低内存占用并提高程序运行效率。这种模式适用于当一个应用程序使用了大量的相似对象时,通过共享这些对象的部分状态,使得在不牺牲可操作性的前提下,大幅度地减少对象的数量。
核心思想与实现步骤:
- 区分内部状态和外部状态:
- 内部状态(Intrinsic State):是对象自身所固有的,不会随环境改变而改变,可以被共享,不影响对象的独立性。例如,在绘制图形应用中,颜色、线型等属性就是内部状态。
- 外部状态(Extrinsic State):是随环境改变而改变的,不能被共享,依赖于具体场景。例如,在上述图形应用中,图形的位置就是外部状态。
- 创建享元类: 定义一个享元类,该类包含内部状态并提供访问、操作内部状态的方法。享元类通常为抽象类或接口,以便于扩展和实现多态。
- 创建享元池(Flyweight Pool): 创建一个享元池来存储和管理享元对象。当客户端请求一个对象时,首先检查享元池中是否存在满足条件的对象。如果存在,则直接返回;否则,创建一个新的享元对象加入到享元池中,并返回给客户端。
- 客户端使用享元对象: 客户端通过传入外部状态给享元对象,使其能够根据外部状态进行相应的操作。享元对象处理内部状态,而客户端负责管理外部状态。
示例代码(以字符画为例):
// 享元接口 public interface CharacterShape { void draw(char c, int x, int y); // 外部状态(位置) } // 具体享元类(如:圆形字符) public class CircleCharacter implements CharacterShape { private char symbol; // 内部状态 public CircleCharacter(char symbol) { this.symbol = symbol; } @Override public void draw(char c, int x, int y) { System.out.println("Drawing character " + symbol + " at (" + x + ", " + y + ")"); } } // 享元工厂 public class CharacterFactory { private Map<Character, CharacterShape> pool = new HashMap<>(); public CharacterShape getCharacter(char c) { if (!pool.containsKey(c)) { pool.put(c, new CircleCharacter(c)); } return pool.get(c); } } // 客户端使用 public class Client { public static void main(String[] args) { CharacterFactory factory = new CharacterFactory(); CharacterShape circleA = factory.getCharacter('A'); CharacterShape circleB = factory.getCharacter('B'); circleA.draw('A', 0, 0); circleB.draw('B', 5, 7); // 后续绘制大量不同位置的'A'和'B'字符,享元池会复用已创建的CircleCharacter对象 } }
在这个例子中,
CharacterShape
是享元接口,CircleCharacter
是具体的享元类,包含内部状态(字符符号)。CharacterFactory
作为享元工厂,维护一个享元池(Map<Character, CharacterShape>
),根据传入的字符创建或获取已存在的享元对象。客户端通过享元工厂获取享元对象,并传入外部状态(位置)进行绘制。总结来说,享元设计模式通过共享内部状态相同的对象,避免大量相似对象的创建,有效地节省了内存空间,特别适用于大规模对象的场景。但需要注意的是,引入享元模式会增加系统的复杂性,需要权衡是否值得为了性能优化而增加设计复杂性。
2.7.2、举例
// 抽象享元 Flyweight
public abstract class Stu {
protected String grade;
public Stu(String grade) {
this.grade = grade;
}
}
// 具体享元 Concrete Flyweight
public class Student extends Stu{
private String name;
private String gender;
public Student(String grade) {
super(grade);
}
public Student(String grade, String name, String gender) {
super(grade);
this.name = name;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public void print(){
System.out.println( "{" +
"grade='" + grade + '\'' +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
'}');
}
}
// 享元工厂 Flyweight Factory
public class StudentFactory {
private static final Map<String, Stu> cache = new HashMap<>();
public static Student getStudent(String grade){
if (cache.containsKey(grade)){
return (Student) cache.get(grade);
}else {
Stu student = new Student(grade);
cache.put(grade,student);
return (Student) student;
}
}
}
// 客户端 Client
public static void main(String[] args) {
Student zs = StudentFactory.getStudent("1");
zs.setGender("boy");
zs.setName("zs");
System.out.println("zs = " + zs);
zs.print();
System.out.println("------------------------------");
Student lisi = StudentFactory.getStudent("2");
lisi.setGender("girl");
lisi.setName("lisi");
System.out.println("lisi = " + lisi);
lisi.print();
System.out.println("------------------------------");
Student zhangsan = StudentFactory.getStudent("1");
zhangsan.setGender("boy");
zhangsan.setName("zhangsan");
System.out.println("zhangsan = " + zhangsan);
zhangsan.print();
System.out.println("===============================");
System.out.println("zs = " + zs);
zs.print();
}
我的理解:
享元模式, 通过key从缓存中获取对象,没有匹配到,就重新创基对象,然后再加入缓存。内部环境指的是对象的共性,不会被外部操作改变,外部环境指的是个性。但是享元模式,由于从内存中共享变量,所以,他们的引用都会指向同一个对象,存在线程安全问题,只是减少了创建对象的数量。
2.8、小结
-
适配器
通过接口的转换,可以使得三角插头既可以使用三孔插座,也可以使用两孔插座;手机充电器,既可以给安卓手机充电,也可以给苹果手机充电
-
装饰
把一个对象,一般通过构造注入,进行包装升级。比如,一杯简单的柠檬水 5 块钱 ,加冰就卖 8 块,加一片柠檬卖 25 块,再加一片绿叶卖 50
-
代理
代理模式,现实的中间商赚差价,比如租房中介,一个月房租当中介费,淦!静态代理,需要为每一个被代理对象,创建一个代理;动态代理,可以不用。
-
外观
外观模式,像是资源的整合,自己本身不实现业务,只是把一系列需要一个一个执行的逻辑或方法,给你统一执行。
-
桥接
什么是桥接模式?百思不得其解,百度,知乎,csdn,维基百科,通义千问:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
桥接设计模式主要解决的是如何在软件设计中有效地处理具有多个独立变化维度的场景,避免因传统继承方式带来的复杂性和局限性,确保各维度变化的独立性,以及提升系统的可扩展性和可维护性。它通过合理的结构设计,使得抽象部分与实现部分能够灵活组合,适应不断变化的需求。
一个产品,比如小米汽车,有标准版、pro版、max版,他们的配置各不相同,多维度变化,所以,在构造小米汽车,引用的都是抽象的基类,如果都是引用到具体的类的话,百万个零部件,会造成类的爆炸。
-
组合
组合模式,相当于套娃,套了一群娃,与单链表相似,更具体的话,文件和文件夹更像。
public interface IFileFolder { /** * 新建文件或文件夹 * @return */ boolean addNewFileFolder(BaseFile file) throws IOException; /** * 删除文件或文件夹 * @return */ boolean dlefile(BaseFile file) throws IOException; /** * 查看当前文件和子文件 */ void show(int deep,String tn) throws IOException; } public abstract class BaseFile implements IFileFolder { protected String pathOrName; protected FileType type; private List<BaseFile> list; public BaseFile(String pathOrName, FileType type) { this.pathOrName = pathOrName; this.type = type; this.list = new ArrayList<>(); } // 省略get|set } public enum FileType { FILE, DIR } public class FileFolder extends BaseFile implements IFileFolder { public FileFolder(String pathOrName, FileType type) { super(pathOrName, type); } @Override public boolean addNewFileFolder(BaseFile baseFile) throws IOException { // 添加文件 if (baseFile.getType().name().equals(FileType.FILE.name())) { // 就挂在当前的文件夹下 File file = new File(this.getPathOrName() + "/" + baseFile.getPathOrName()); // // 创建文件 if (file.exists() || file.createNewFile()) { if (!getList().contains(baseFile)) { getList().add(baseFile); } return true; } } else { // 添加文件夹 // 添加的文件夹,里面有子内容,比如还有文件夹或者文件 if (baseFile.getList().size() > 0) { for (BaseFile file : getList()) { return baseFile.addNewFileFolder(file); } } // 没有子内容了,创建文件夹 File file = new File(this.getPathOrName() + "/" + baseFile.getPathOrName()); if (file.exists() || file.mkdir()) { baseFile.setPathOrName(file.getAbsolutePath()); if (!getList().contains(baseFile)) { getList().add(baseFile); } return true; } } return false; } @Override public boolean dlefile(BaseFile baseFile) throws IOException { // 删除的是文件 if (baseFile.getType().name().equals(FileType.FILE.name())) { return new File(this.getPathOrName() + "/" + baseFile.getPathOrName()).delete(); } else { // 删除的是文件夹 if (baseFile.getList().size() > 0) { // 文件夹里面还有子内容 List<BaseFile> list = baseFile.getList(); // 使用list自己的迭代器,避免并发修改异常 Iterator<BaseFile> iterator = list.iterator(); while (iterator.hasNext()) { BaseFile next = iterator.next(); baseFile.dlefile(next); iterator.remove(); } } // 文件夹里面没有子内容 return new File(baseFile.getPathOrName()).delete(); } } /** * 打印 * @param deep * @throws IOException */ @Override public void show(int deep, String tn) throws IOException { int lastIndexOf = this.getPathOrName().lastIndexOf("/"); if (lastIndexOf == -1) { lastIndexOf = this.getPathOrName().lastIndexOf("\\"); } String dir = tn + "|--" + this.getPathOrName().substring(lastIndexOf + 1); System.out.println(dir); if (deep == 0) { return; } for (BaseFile baseFile : getList()) { if (baseFile.getType().name().equals(FileType.DIR.name())) { int tmp = deep; baseFile.show(--tmp, tn + "\t"); } else { baseFile.show(deep, tn + "\t"); } } } } public class LeafFile extends BaseFile implements IFileFolder { public LeafFile(String pathOrName, FileType type) { super(pathOrName, type); } @Override public boolean addNewFileFolder(BaseFile baseFile) { return false; } @Override public boolean dlefile(BaseFile baseFile) { return false; } @Override public void show(int deep, String tn) throws IOException { for (int i = deep; i > 0; i--) { System.out.print(tn + "|--" + this.getPathOrName() + "\n"); if (this.getList().size() > 0) { for (int j = 0; j < this.getList().size(); j++) { this.getList().get(j).show(--deep, tn); } } else { break; } } } } public class CombineTest { public static void main(String[] args) throws IOException { String path = ClassLoader.getSystemClassLoader().getResource("").getPath(); BaseFile fileFolder = new FileFolder(path+"demo", FileType.DIR); BaseFile a = new LeafFile("a.txt",FileType.FILE); BaseFile b = new LeafFile("b.txt",FileType.FILE); BaseFile folder3= new FileFolder("folder3", FileType.DIR); fileFolder.addNewFileFolder(a); fileFolder.addNewFileFolder(b); fileFolder.addNewFileFolder(folder3); folder3.addNewFileFolder(a); BaseFile folder4= new FileFolder("folder4", FileType.DIR); BaseFile folder5= new FileFolder("folder5", FileType.DIR); folder3.addNewFileFolder(folder4); folder4.addNewFileFolder(folder5); folder5.addNewFileFolder(b); // 打印 fileFolder.show(4,"\t"); } }
-
享元
享元模式,目的是减少对象的创建,使用缓存中对象。享元模式,就像是工厂模式的升级版本,增加了缓存。
3、行为型模式(11种)
3.1、策略
策略模式(Strategy Pattern)
3.1.1、定义:
菜鸟教程:
意图:
定义一系列的算法,把他们一个个封装起来,并且使他们可以相互替换
主要解决:
在有多种算法相似的情况下,使用if ...else 所带来的复杂和难以维护
核心角色:
- 环境(Context): 维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入,简单工厂等方式来获取具体策略的对象。
- 抽象策略(Abstract Strategy): 定义了抽象的公共接口或抽象类,规定了具体策略类必须实现的方法
- 具体测试 (Concrete Strategy): 实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。
重构大师:https://refactoringguru.cn/design-patterns/strategy
意图:
策略模式是一种行为设计模式,它能够让你定义一系列算法,并将每种算法分别放入独立的类中,使用算法对象能够互相替换。
策略模式,就是对于一个接口,有很多的实现,选择不同的实现,具体使用哪一个。
3.1.2、举例:
实现不同的登录方式:
public interface ILogin {
void login();
}
public class EmailLogin implements ILogin{
@Override
public void login() {
System.out.println("邮箱登录");
}
}
public class AccountLogin implements ILogin{
@Override
public void login() {
System.out.println("账号登录!");
}
}
public class PhoneLogin implements ILogin{
@Override
public void login() {
System.out.println("手机号登录!");
}
}
public class LoginContext {
private ILogin login;
public LoginContext(ILogin login) {
this.login = login;
}
public void login() {
login.login();
}
}
public class LoginTest {
public static void main(String[] args) {
LoginContext loginContext = new LoginContext(new AccountLogin());
loginContext.login();
}
}
我的理解
策略模式是针对接口的实现,不同方式或算法的实现。工厂模式是有点像,但是工厂主要注重的是对象的创建。
到底执行哪一个策略,可以由系统环境、参数配置或者客户端传参来决定
3.2、模板方法
模板方法模式 Template Method
3.2.1、定义
菜鸟教程:
意图:
定义一个操作总的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以改变一个算法的结构即可重定义改算法的某些特定步骤。
重构大师:https://refactoringguru.cn/design-patterns/template-method
意图:
模板方法模式是一种行为设计模式, 它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。
从菜鸟教程中,可以看到,模板方法,只在抽象层,定义了一个模板的执行方法,调用抽象的模板方法,按照固定的顺序执行对应的逻辑。
public abstract class Game {
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
//模板
public final void play(){
//初始化游戏
initialize();
//开始游戏
startPlay();
//结束游戏
endPlay();
}
}
🚩注意,模板方法申明 final ,不能被重写。
3.2.2、举例
在项目中,用户注册案例:
1、创建用户信息
2、创建用户角色信息
3、创建用户部门信息
4、创建用户团队信息
5、创建用户财务信息
具体的实现,可以使用策略模式,但是实现的步骤,从1-5一步一步进行,使用模板方法定义执行逻辑
public abstract class IAuth {
// 创建用户
public abstract void createUser(User user);
// 创建用户角色
public abstract void createRole(User user);
// 创建用户部门信息
public abstract void createDepatment(User user);
// 创建用户团队
public abstract void createTeam(User user);
// 创建用户财务
public abstract void createFinance(User user);
// final 定义模板方法,指定注册用户的逻辑
public final void register(User user){
createUser(user);
createRole(user);
createDepatment(user);
createTeam(user);
createFinance(user);
}
}
public class RegisterNormalService extends IAuth {
@Override
public void createUser(User user) {
System.out.println("创建用户,用户名:"+user.getName()+",密码:"+user.getPassword());
}
@Override
public void createRole(User user) {
System.out.println("创建用户角色。。。");
}
@Override
public void createDepatment(User user) {
System.out.println("创建用户部门。。。");
}
@Override
public void createTeam(User user) {
System.out.println("创建用户团队。。。");
}
@Override
public void createFinance(User user) {
System.out.println("创建用户财务。。。");
}
}
public class User {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
public class UserClient {
public static void main(String[] args) {
IAuth auth = new RegisterNormalService();
auth.register(new User("张师傅","123456"));
}
}
我的理解:
模板方法,在抽象层,使用final 定义具体的执行逻辑,调用的也是抽象的方法,关于对应的实现并不关心。
3.3、观察者
观察者模式 Observer
3.3.1、定义
菜鸟教程:
观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,当一个对象状态发生改变时,其所依赖都会收到通知并自动更新.
当对象间存在一对多关系时,则使用观察者模式Observer Pattern,比如,当一个对象修改时,则会自动通知依赖他的对象
**意图:**定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
**主要解决:**一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
**何时使用:**一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。
**如何解决:**使用面向对象技术,可以将这种依赖关系弱化。
**关键代码:**在抽象类里有一个 ArrayList 存放观察者们。
核心角色:
- 主题(Subject): 也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
- 观察者(Oberver): 观察者是接收主题通知的对象。观察者需要实现一个更新的方法,当收到主题的通知时,调用改方法进行更新操作。
- 具体主题(Concrete Subject): 具体主题是主题的实现类,它维护着观察者列表,并在状态改变时通知观察者。
- 具体观察者(Concrete Observer): 具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。
重构大师:https://refactoringguru.cn/design-patterns/observer
意图:观察者模式是一种行为设计模式,允许你定义一种订阅机制,可以在对象事件发生时通知多个“观察”改对象的其他对象
发布者 (Publisher) 会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。
订阅者 (Subscriber) 接口声明了通知接口。 在绝大多数情况下, 该接口仅包含一个 update更新方法。 该方法可以拥有多个参数, 使发布者能在更新时传递事件的详细信息。
3.3.2、举例
基于消息的发布和订阅。
主题政府,发布的消息内容是,五一放假5天
观察者有,学校、企业
public class Government {
// 所有观察者
private List<MsgListen> list = new ArrayList<>();
// 添加观察者
public boolean add(MsgListen listen){return list.add(listen);}
// 通知观察者
public void notifyListen(String msg){
for (MsgListen msgListen : list) {
msgListen.listen(msg);
}
}
}
public interface MsgListen {
void listen(String msg);
}
public class CompanyListen implements MsgListen{
@Override
public void listen(String msg) {
System.out.println("【企业】接收到通知:"+msg);
}
}
public class SchoolListen implements MsgListen{
@Override
public void listen(String msg) {
System.out.println("【学校】接收到通知:"+msg);
}
}
public class NotifyClinet {
public static void main(String[] args) {
Government government = new Government();
government.add(new SchoolListen());
government.add(new CompanyListen());
government.notifyListen("五一放假5天");
}
}
我的理解
观察者模式,针对一个对象的改变,进行通知其他对象来做出响应的操作。发布消息即主题内部维护着所有的观察者,然后进行一个一个通知。
3.4、迭代器
迭代器模式(Iterator Pattern)
3.4.1、定义:
菜鸟教程:
意图:提供一种方法顺序访问一个聚合对象中各个元素,而又无需暴露改对象的内部表示
关键代码:hasNext 和 next
注意事项:
迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代类负责,这样既可以做到不暴露集合内部结构,又可以让外部透明地访问集合内部数据
重构大师:https://refactoringguru.cn/design-patterns/iterator
意图:让你在不暴露集合底层表现实行的情况下遍历集合中所有的元素。
通义千问:
迭代器设计模式是一种行为设计模式,它提供了一种方法来顺序访问聚合对象(如列表、集合、树等)中的元素,而无需暴露该对象的内部表示(如数组、链表、哈希表等)。迭代器模式使得客户端代码能够以一致的方式遍历不同的数据结构,同时隐藏了数据结构的具体实现细节,增强了代码的可复用性和可扩展性。
核心角色:
- 迭代器接口(Iterator): 定义了访问和遍历元素所需的通用方法
- hasNext(): 检查是否存在下一个元素
- next() : 返回下一个元素并向前移动迭代的位置
- 可能还包括 remove() 删除元素,或者 peek()查看下一个元素,但是不移动迭代器
- 具体迭代器(Concrete Iterator): 实现了迭代器接口,与特定的聚合结构紧密关联,负责跟踪当前遍历位置和管理迭代状态
- 聚合(Aggregate) 接口:定义了创建迭代器对象的接口,通常包含一个方法
- createIterator() : 返回一个与该聚合类型对应的迭代器实例
- 具体聚合类(Conrete Aggregate): 实现了聚合接口,存储了实际的数据元素,并在 createItertor() 方法中返回对应的具体迭代器实例
工作流程
- 客户端代码请求聚合对象(如列表、集合)创建一个迭代器。
- 聚合对象调用
createIterator()
方法,返回一个与自身数据结构相匹配的具体迭代器实例。- 客户端使用迭代器提供的方法(如
hasNext()
和next()
)来遍历聚合中的元素。每次调用next()
方法时,迭代器负责移动到下一个元素,并返回该元素。- 遍历过程中,客户端无需关心聚合对象的内部结构(如是否为数组、链表或哈希表),只需通过迭代器接口进行操作。
优点
- 封装性:隐藏了聚合对象的内部表示,客户端仅通过迭代器接口与数据结构交互。
- 一致性:为不同的数据结构提供了统一的遍历接口,使代码易于理解和维护。
- 灵活性:允许在不影响客户端代码的情况下,更换、扩展或修改聚合对象的内部实现。
- 支持多种遍历方式:可以通过定义不同的迭代器类来实现对同一聚合对象的不同遍历策略(如正序、逆序、深度优先等)。
在Java中,迭代器模式被广泛应用于标准库中的集合框架。例如,java.util.List
、java.util.Set
和 java.util.Map
接口都提供了 iterator()
方法来创建相应的迭代器
3.4.2、举例
使用迭代器,对数组进行操作
- 迭代器接口
public interface ArrIterator<T> { // 判断是否有下一个 public boolean hasNext(); // 获取下一个,指针会向后移动 public T next(); // 获取下一个,指针不变 public T peek(); // 遍历到当前元素,进行删除 public boolean remove(); // 遍历到当前元素,在当前元素之前添加 public void beforeAttach(T t,T o); // 遍历到当前元素,在当前元素之后添加 public void afterAttach(T t,T o); // 重置游标 public void reset(); }
- 聚合接口,获取迭代器
public interface ArrContainer{ ArrIterator iterator(); }
- 具体迭代器实现
public class ArrClient<T> implements ArrContainer { T[] arr; int cursor; public ArrClient(T[] arr) { this.arr = arr; this.cursor = -1; } @Override public ArrIterator<T> iterator() { return new iter(); } private class iter implements ArrIterator { @Override public boolean hasNext() { return arr.length - 1 > cursor; } @Override public T next() { if (!hasNext()) { return null; } return (T) arr[++cursor]; } @Override public T peek() { if (hasNext()) { int index = cursor; return (T) arr[++index]; } return null; } @Override public boolean remove() { int index = cursor; if (!hasNext()) { return false; } // 当前是第一个元素,没有下一个元素 if (cursor == 0 && peek() == null) { arr = (T[]) new Object[]{}; } else if (cursor == 0 && peek() != null) //还有下一个元素 { T[] temp = (T[]) new Object[arr.length - 1]; while (cursor < arr.length - 1) { temp[cursor] = arr[++cursor]; } arr = temp; } // 当前是中间元素 if (cursor > 0 && cursor < arr.length - 1) { T[] temp = (T[]) new Object[arr.length - 1]; for (int i = 0; i < temp.length; i++) { if (i < cursor) { temp[i] = arr[i]; } else if (i >= cursor) { temp[i] = arr[++cursor]; } } arr = temp; } // 当前是最后一个元素 if (cursor == arr.length - 1) { T[] temp = (T[]) new Object[arr.length - 1]; for (int i = 0; i < arr.length - 2; i++) { temp[i] = arr[i]; } arr = temp; } cursor = index - 1; return true; } /** * 在当前元素之前插入元素 */ @Override public void beforeAttach(Object t, Object o) { // 当前元素之前的位置不变,当前以及之后的所有的元素往后移动 T[] temp = (T[]) new Object[arr.length + 1]; while (hasNext()) { T next = next(); if (next == t) { break; } } for (int i = 0; i < temp.length && cursor < arr.length; i++) { if (i < cursor) { temp[i] = arr[i]; } else if (i == cursor) { temp[i] = (T) o; } else { temp[i] = arr[cursor]; cursor++; } } arr = temp; } @Override public void afterAttach(Object t, Object o) { // 当前之后的所有的元素往后移动 T[] temp = (T[]) new Object[arr.length + 1]; while (hasNext()) { T next = next(); if (next == t) { break; } } for (int i = 0; i < temp.length && cursor < arr.length; i++) { if (i <= cursor) { temp[i] = arr[i]; } else if (i == cursor + 1) { temp[i] = (T) o; } else { temp[i] = arr[++cursor]; } } arr = temp; } @Override public void reset() { cursor = -1; } } }
- 测试
public class Demo { public static void main(String[] args) { String[] arr = {"a", "b", "c","d"}; ArrClient<String> stringArrClient = new ArrClient(arr); ArrIterator<String> iterator = stringArrClient.iterator(); iterator.afterAttach("a","k"); iterator.reset(); while (iterator.hasNext()){ String str = iterator.next(); System.out.println("str = " + str); } } }
我的理解
迭代器模式,主要是针对集合容器进行遍历。当使用for 、foreach、fori 等方式进行遍历集合的时候,如果一边遍历一边进行修改,可以能会触发并发修改异常。
3.5、责任链
责任链模式 Chain of Responsibility
3.5.1、定义:
菜鸟教程:
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。通常每一个接收者都包含对另一个接受者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传递到下一个接收者,以此类推。
意图:
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象链接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
**主要解决:**职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
**何时使用:**在处理消息的时候以过滤很多道。
**如何解决:**拦截的类都实现统一接口。
**关键代码:**Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
核心角色:
- 抽象处理者(Handler)
定义一个处理请求的接口,通常包含一个处理请求的方法,比如:handleRequest , 和指向下一个处理者的引用(后继者)
- 具体处理者 ConcreteHandler
实现了抽象处理者的接口,负责处理请求。如果能够处理该请求,则直接处理;否则将请求传递到下一个处理者
- 客户端 client
创基处理者对象,并将他们链接成一条链。通常,客户端只需要将请求发送给责任链的第一个处理者,无需关心请求的具体处理过程。
重构大师:https://refactoringguru.cn/design-patterns/chain-of-responsibility
意图:责任链是行为设计模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可以对请求进行处理,或将其传递给链上的下个处理者。
通义千问:
责任链设计模式(Chain of Responsibility Pattern)是一种行为型设计模式,其核心思想是通过构建一系列对象(称为处理器或处理节点),形成一条链状结构。当一个请求到来时,该请求会沿着链路从一个对象传递到下一个对象,直至找到能够处理请求的合适对象。这种模式的主要目的是实现请求的发送者与接收者的解耦,允许请求在多个潜在的处理者之间动态地传递,直到得到处理或到达链的末端。
以下是责任链模式的关键要素和特点:
主要角色:
- Handler(抽象处理器):定义了一个处理请求的接口,通常包括一个
handleRequest()
方法。同时,它可能包含一个指向链中下一个处理器的引用,以便将请求传递下去。- Concrete Handlers(具体处理器):实现了抽象处理器接口,它们可以是链中的各个节点,负责对特定类型的请求进行处理。每个具体处理器在收到请求后,检查是否符合自身处理条件;如果能够处理,则执行相应操作并返回结果;若无法处理,则通过持有的下一个处理器引用将请求转发给链中的下一个节点。
- Client(客户端):创建责任链,并向链首的处理器提交请求。客户端通常并不知道请求具体由哪个处理器处理,仅与链的起点交互。
工作流程:
- 客户端创建一个请求,并将其发送给责任链的第一个处理器。
- 第一个处理器检查请求,如果能够处理,就执行相应的操作并返回结果,处理过程结束。
- 如果第一个处理器无法处理该请求,它将请求传递给链中的下一个处理器,依此类推。
- 这个过程沿着链路持续进行,直到找到能够处理请求的处理器,或者到达链尾没有更多处理器可以处理。
优点:
- 松散耦合:请求发送者无需了解接收者细节,只需将请求发送到链的起始点。
- 灵活扩展:可以通过添加新的处理器类轻松地向系统中添加新的请求处理逻辑,而无需修改现有代码。
- 动态组合:责任链可以在运行时动态地改变其结构,如插入、移除处理器节点,适应不同的处理需求。
- 单一职责原则:每个处理器只负责处理与其能力相匹配的请求,简化了单个类的设计。
应用场景:
- 日志记录:不同级别的日志事件可以由不同处理器处理,如低级别日志直接写入文件,严重错误触发邮件通知等。
- 权限验证:多个权限规则按顺序检查用户的访问请求,任何一个规则批准即可通过验证。
- Web框架中的过滤器/拦截器链:如Spring Boot中的拦截器链,对HTTP请求进行预处理、身份验证、日志记录等。
- 事件处理系统:事件按照类型被不同的处理器消费,如UI事件、系统事件、网络事件等。
- 支付风控:交易请求经过多级风险评估,如反欺诈检测、信用评分、账户状态检查等。
责任链模式通过构建一个动态的责任链,使得请求能够在多个候选处理器之间透明地传递,直到被有效处理,从而实现了请求处理逻辑的分散、可配置和可扩展性。这种模式在需要灵活处理多种请求类型且请求处理者可能频繁变动的场景中尤为适用。
3.5.2、举例
实现用户请求系统版本号,进行拦截校验
- token 校验
- ip校验
- 权限校验
public interface IAuthHandler { public boolean handler(Req req); } public abstract class HandlerMiddler implements IAuthHandler{ protected HandlerMiddler next; protected static HandlerMiddler getLink(HandlerMiddler ... handlerMiddlers){ HandlerMiddler first = handlerMiddlers[0]; HandlerMiddler head = handlerMiddlers[0]; for (int i = 1; i < handlerMiddlers.length; i++) { head.next = handlerMiddlers[i]; head = handlerMiddlers[i]; } return first; } } public class IpMiddle extends HandlerMiddler { @Override public boolean handler(Req req) { if ("127.0.0.1".equals(req.getIp())){ System.out.println("ip验证通过~"); if (this.next != null){ return this.next.handler(req); } return true; } System.out.println("非法ip,拒绝访问!"); return false; } } public class TokenMiddle extends HandlerMiddler { @Override public boolean handler(Req req) { if ("abc".equals(req.getToken())){ System.out.println("token验证通过~"); if (this.next != null){ return this.next.handler(req); } return true; } System.out.println("请重新登录~"); return false; } } public class PermissionMiddle extends HandlerMiddler { @Override public boolean handler(Req req) { if ("*".equals(req.getPermiss())){ System.out.println("权限验证通过~"); if (this.next != null){ return this.next.handler(req); } return true; } System.out.println("无权访问!"); return false; } } public class ServerSys { private static final ServerSys instance = new ServerSys(); private ServerSys(){} public static ServerSys getInstance(){ return instance; } public String getVersion(){ return "v1.6.63"; } } public class SysProxy { private HandlerMiddler handlerMiddler; public SysProxy(HandlerMiddler handlerMiddler) { this.handlerMiddler = handlerMiddler; } public String getVersion(Req req){ boolean check = handlerMiddler.handler(req); if (check){ return ServerSys.getInstance().getVersion(); } // 报错 return ""; } } public class Req { private String token; private String ip; private String permiss; public Req(String token, String ip, String permiss) { this.token = token; this.ip = ip; this.permiss = permiss; } // 省略get和set } public class Demo { public static void main(String[] args) { HandlerMiddler handle = HandlerMiddler.getLink( new TokenMiddle(), new PermissionMiddle(), new IpMiddle() ); SysProxy proxy = new SysProxy(handle); String version = proxy.getVersion(new Req("abc", "127.0.0.1", "a")); System.out.println("version = " + version); } }
运行结果:
我的理解
责任链模式,是一条链子,会进行传递下去。【重构大师】中说,链子中的每一个节点,
应该清楚:
1、是否处理这个请求(比如Logger,打印的级别是否是我当前需要处理的级别)。
2、是否将该请求沿着链进行传递下去(比如拦截器)
构建责任链链的方式,就如同构建单链表,当前对象,有一个next指针指向下一个处理者,直到链尾。
3.6、命令
命令模式 Command
3.6.1、定义
菜鸟教程:
命令模式Command Pattern 是一种数据驱动的设计模式,它属于行为型模式。请求一命令的形式包裹在对象中,并传递给调用对象。
调用对象寻找可以处理命令的适合对象,并把该命令传给响应的对象,该对象执行命令。
意图:
将一个请求封装成对象,从而使您可以用不同的请求对客户进行参数化
核心角色:
- 命令 Command :
定义了执行操作的接口,通常包含一个 execute 方法,用于调用具体的操作。
- 具体命令 ConcreteCommand :
实现了命令接口,负责执行具体的操作。它通常包含对接收者的引用,通过调用接收者的方法来完成请求的处理
- 接收者 Receiver
知道如何执行与请求的相关操作,实际执行命令的对象
- 调用者/请求者 Invoker :
发送命令的一些,它包含了一个命令对象并能触发命令的执行。调用者并不直接处理请求,而是通过将请求传递给命令对象来实现。
- 客户端 Client :
创建具体命令对象并设置其接收者,将命令对象交给调用者执行。
重构大师: https://refactoringguru.cn/design-patterns/command
命令模式是一种行为设计模式,它可以将请求转换为一个包含与请求相关所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。
百度百科:https://baike.baidu.com/item/%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F/7277118
模式结构
Command:
定义命令的接口,声明执行的方法。
ConcreteCommand:
命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
Receiver:
接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
Invoker:
要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client:
创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
具体的命令实现接口,并非真正的实现,有一个引用指向真正的接收者,调用接收者来实现。
3.6.2、举例
参考博客:https://blog.csdn.net/chentian114/article/details/83068503
餐厅点餐:
请求者:【顾客】,请求的内容:“老表,干炒牛河”
接收者:【大厨】 ,但是厨师不和顾客直接沟通,需要通过服务员
服务员:记录A顾客需要一份干炒牛河,传到后厨
public interface ICommand { // 烹饪佳肴 public void cook(); } public class ConcreteCook implements ICommand{ private AMeal meal; public ConcreteCook(AMeal meal) { this.meal = meal; } @Override public void cook() { meal.cook(); } } public abstract class IMeal { public IMeal(String name) { this.name = name; } // 菜名 protected String name; // 烹饪佳肴 protected abstract void cook(); } public class AMeal extends IMeal{ // 价格 private Double price; public AMeal(String name, Double price) { super(name); this.price = price; } @Override protected void cook() { System.out.println("烹饪: "+name+",售价:"+price); } } public class Client { public static void main(String[] args) { AMeal meal = new AMeal("干炒牛河",52.6); ConcreteCook concreteCook = new ConcreteCook(meal); concreteCook.cook(); } }
我的理解
为什么不直接调用 receiver.action() 呢?虽然解耦了,但是有一种脱裤子放屁的感觉。
所以,我认为命令模式,使用调度invoker进行要么通过构造,要么通过set具体的命令,而命令会有一个接收者即处理具体业务逻辑的引用,命令本身不处理逻辑。所以,命令模式通过一层套一层的方式,实现了请求与接收者的解耦。
3.7、备忘录
备忘录模式 Memento
3.7.1、定义
菜鸟教程:
在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样可以在以后将对象恢复到原先保存的状态。
关键角色:
- 发起人Oringinator: 负责创建一个备忘录对象,用于保存自身的状态,并可以通过备忘录对象恢复到之前的状态
- 备忘录 Memento:用于存储发起人对象的内部状态,并提供给发起人对象访问其内部状态的接口
- 管理者 Caretake : 负责保存备忘录对象,但不能对备忘录对象进行操作或检查其内容。
tips: 管理者,可以使用stack作为容器,撤销上一次操作,就从栈中pop出来。
3.7.2、举例
- 遇到 1 认为此路不通,遇到 0 认为可以走
- 走动一个格子,就把该格子压入栈中,(可以理解使用备忘录小抄记录一下,然后把这个备忘录丢到管理备忘录容器里面)
- 走动当前格子,压入栈后,把当前格子数值标记为 6 ,这样,在打印这个二位数组就可以看到走过的路程了
- 当此路不通,就撤退(从备忘录里面取出上一次记录的格子),然后把当前位置的数值恢复到原来的 0
- 重复上述步骤,直到走出迷宫
1、准备一个迷宫
public class MazeArray { // 迷宫X轴长度 private int x; // 迷宫Y轴长度 private int y; // 迷宫棋盘 private final int[][] MAZE; public MazeArray(int x, int y) { this.x = x; this.y = y; MAZE = new int[this.x][this.y]; init(); } /** * 初始化棋盘 */ private void init() { // 第一步,全部赋值1,默认 for (int i = 0; i < this.MAZE.length; i++) { for (int j = 0; j < this.MAZE.length; j++) { this.MAZE[i][j] = 1; } } // 设定迷宫可走路线 this.MAZE[2][0]=0; this.MAZE[2][1]=0; this.MAZE[2][2]=0; this.MAZE[2][3]=0; this.MAZE[2][4]=0; this.MAZE[2][5]=0; this.MAZE[2][6]=0; this.MAZE[2][7]=0; this.MAZE[2][8]=0; this.MAZE[2][9]=0; this.MAZE[3][3]=0; this.MAZE[4][3]=0; this.MAZE[5][3]=0; this.MAZE[6][3]=0; this.MAZE[7][3]=0; this.MAZE[8][3]=0; this.MAZE[9][3]=0; this.MAZE[8][4]=0; this.MAZE[8][5]=0; this.MAZE[8][6]=0; this.MAZE[8][7]=0; this.MAZE[8][8]=0; this.MAZE[8][9]=0; this.MAZE[3][9]=0; this.MAZE[4][9]=0; this.MAZE[5][9]=0; this.MAZE[9][6]=0; this.MAZE[10][6]=0; } /** * 打印棋盘 */ public void printMaze() { for (int i = 0; i < this.MAZE.length; i++) { for (int j = 0; j < this.MAZE.length; j++) { System.out.print(this.MAZE[i][j] + "\t"); } System.out.println(); } } }
2、准备一个探索者,进行闯迷宫
/** * 探索者 */ public class Explorer implements Serializable { private static final MemenContainer CONTAINER = MemenContainer.getInstance(); private int x; // x轴 private int y; // y轴 public Explorer(int x, int y) { this.x = x; this.y = y; } /** * 进行行走 * * @param array 棋盘 * @return */ public void run(MazeArray array) { // 向东走 E if (this.y + 1 < array.getMAZE().length && array.getMAZE()[this.x][this.y + 1] == 0) { saveRoute(array, "E"); this.y = this.y + 1; System.out.println("向东走~~~"); run(array); } // 向南走 S if (this.x + 1 < array.getMAZE().length && array.getMAZE()[this.x + 1][this.y] == 0) { saveRoute(array, "S"); this.x = this.x + 1; System.out.println("向南走~~~"); run(array); } // 向西走 W if (this.y - 1 >= 0 && array.getMAZE()[this.x][this.y - 1] == 0) { saveRoute(array, "W"); this.y = this.y - 1; System.out.println("向西走~~~"); run(array); } // 向北走 N if (this.x - 1 >= 0 && array.getMAZE()[this.x - 1][this.y] == 0) { saveRoute(array, "N"); this.x = this.x - 1; System.out.println("向北走~~~"); run(array); } // 终点 this.MAZE[10][6]; if (this.x == 10 && this.y == 6 ) { array.getMAZE()[this.x][this.y] = 6; array.printMaze(); System.exit(0); } // 此路不通了,撤退 retreat(array); } /** * 保存行走过的坐标 */ public void saveRoute(MazeArray array, String direction) { array.getMAZE()[this.x][this.y] = 6; // 标记一下走过的,防止走回去 Memen memen = new Memen(this.x, this.y, direction); CONTAINER.add(memen); System.out.println("==================保存备忘录=================" + memen + "==============="); array.printMaze(); } /** * 撤退,此路不通,弹出上一个格子 */ public void retreat(MazeArray array) { // 恢复被标记的地方 Memen memen = CONTAINER.pop(); this.x = memen.getX(); this.y = memen.getY(); array.getMAZE()[this.x][this.y] = 0; System.out.println("===================撤回上一步==================" + memen + "============="); array.printMaze(); } }
3、准备一个备忘录
/** * 备忘录 */ public class Memen { private int x; // x坐标 private int y; // y坐标 private String direction; // 行走的方向 public Memen(int x, int y, String direction) { this.x = x; this.y = y; this.direction = direction; } public int getX() { return x; } public int getY() { return y; } public String getDirection() { return direction; } @Override public String toString() { return "x=" + x + ", y=" + y + ", 方向='" + direction ; } }
4、准备一个备忘录管理者容器
/** * 备忘录容器 */ public class MemenContainer { // 存储备忘录容器 private final Stack<Memen> stack = new Stack<>(); private MemenContainer(){} public static class SingletonHolder{ private static final MemenContainer SINGLETON = new MemenContainer(); } // 全局访问节点 public static MemenContainer getInstance(){ return SingletonHolder.SINGLETON; } /** * 保存一个备忘录 * @param memen */ public void add(Memen memen){ stack.push(memen); } /** * 弹出一个备忘录 * @return */ public Memen pop(){ return stack.pop(); } }
5、测试
public class MazeTest { public static void main(String[] args) { MazeArray mazeArray = new MazeArray(11, 11); Explorer explorer = new Explorer(2,0); // 起点 explorer.run(mazeArray); } }
我的理解
备忘录设计模式,相当于历史记录,可以进行回退,记住之前的状态或地标,比如事务的回滚
3.8、状态
状态模式 State
3.8.1、定义
菜鸟教程
类的行为是基于它的状态改变的 创建各种状态的对象和一个行为随着状态对象改变而改变的context对象
意图:允许对象在内部状态发生改变时改变它的行为,对象好像看起来修改了它的类
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变他的相关行为
如何解决:将各种具体的状态抽象出来
核心角色:
状态接口 (State interface) : 定义了一个所有状态类需要遵守的接口,声明了处理对象状态相关行为的方法
具体状态类 (Concrete State): 实现了状态接口,每一个具体的状态类代表对象的一种具体的状态,封装了该状态对象的行为逻辑
上下文(Context class): 维护一个对状态对象的引用,这个引用指向当前状态对象。上下文定义了与状态相关的操作,这些操作委托给当前状态对象来执行。当上下文的内部状态改变时,它会改变这个引用,从而使得对象的行为随着状态的改变而改变。
重构大师:https://refactoringguru.cn/design-patterns/state
状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态
3.8.2、举例
参考知乎:https://zhuanlan.zhihu.com/p/355381153
台灯状态的转变:微光->亮->强光->关
/**
* 定义状态接口
*/
public interface State {
// 模拟台灯点亮
public void show();
public void click(Context context);
}
/**
* 引用上下文
*/
public class Context {
private State state;
public Context(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void click(){
this.state.click(this);
}
}
public class Light1 implements State{
@Override
public void show() {
System.out.println("微光");
}
@Override
public void click(Context context){
this.show();
context.setState(new Light2());
}
}
....
public class Light4 implements State{
@Override
public void show() {
System.out.println("关");
}
@Override
public void click(Context context){
this.show();
context.setState(new Light1());
}
}
/**
* 测试
*/
public class LightTest {
public static void main(String[] args) {
Context context = new Context(new Light1());
context.click();
context.click();
context.click();
context.click();
context.click();
context.click();
}
}
我的理解
状态设计模式,context上下文内部状态对象的改变,改变对象的行为。我认为可以理解,切换了不同的实现类,自然会执行不同的逻辑。
3.9、访问者
访问者模式 Visitor
3.9.1、定义
菜鸟教程
使用一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。元素对象已接受访问者对象,这样访问者对象可以处理元素对象上的操作
意图:主要将数据结构和数据操作分离
主要解决:稳定的数据结构和易变的操作耦合的问题
何时使用:需要对一个对象结构中的对象进行很多不同且不相关的操作,而需要避免让这些操作“污染”这些对象的类,使用访问者模式将这些封装到类中。
如何解决:在被访问的类里加一个对外提供接待访问者的接口
重构大师: https://refactoringguru.cn/design-patterns/visitor
意图:能将算法与其作用的对象隔离开
结构模式:
3.9.2、举例
-
元素接口
/** * 元素接口 */ public interface IAccept { /** * 接收访问者 * @param visitor */ public void accept(IVisitor visitor); }
-
访问者接口
/** * 访问者接口 */ public interface IVisitor { // 访问三角形面积 void visitTriangle(Triangle triangle); // 访问圆形面积 void visitCircle(Circle circle); // 访问正方形面积 void visitSquare(Square square); // 访问长方形面积 void visitRectangle(Rectangle rectangle); }
-
形状基类
/** * 形状基类 */ public class BaseShape { protected double wide; // 宽 protected double length; // 长 protected double high; // 高 protected double radius; // 半径 protected BaseShape[] shapes; public BaseShape() { } public BaseShape(double wide, double length, double high, double radius) { shapes = new BaseShape[]{new Circle(radius),new Rectangle(wide,length),new Square(length),new Triangle(length,high)}; } public void accept(IVisitor iVisitor){ for (BaseShape shape : shapes) { shape.accept(iVisitor); } } }
-
具体访问者
/** * 具体访问者 */ public class Visitor implements IVisitor { @Override public void visitTriangle(Triangle triangle) { System.out.println("三角形的面积:" + triangle.getArea()); } @Override public void visitCircle(Circle circle) { System.out.println("圆形的面积:" + circle.getArea()); } @Override public void visitSquare(Square square) { System.out.println("正方形的面积:" + square.getArea()); } @Override public void visitRectangle(Rectangle rectangle) { System.out.println("长方形的面积:" + rectangle.getArea()); } }
-
具体元素
/** * 圆形元素 */ public class Circle extends BaseShape implements IAccept { double radius; public Circle(double radius) { this.radius = radius; } @Override public void accept(IVisitor visitor) { visitor.visitCircle(this); } /** * 获取面积 * @return */ public double getArea(){ return (radius * radius) * Math.PI; } } /** * 长方形元素 */ public class Rectangle extends BaseShape implements IAccept { double wide; // 宽 double length; // 长 public Rectangle(double wide, double length) { this.wide = wide; this.length = length; } @Override public void accept(IVisitor visitor) { visitor.visitRectangle(this); } /** * 获取面积 * @return */ public double getArea(){ return wide * length; } } /** * 正方形元素 */ public class Square extends BaseShape implements IAccept { double length; // 长 public Square(double length) { this.length = length; } @Override public void accept(IVisitor visitor) { visitor.visitSquare(this); } /** * 获取面积 * @return */ public double getArea(){ return length * length; } } /** * 三角形元素 */ public class Triangle extends BaseShape implements IAccept { double length; // 长 double high; // 高 public Triangle(double length, double high) { this.length = length; this.high = high; } @Override public void accept(IVisitor visitor) { visitor.visitTriangle(this); } /** * 获取面积 * @return */ public double getArea(){ return (length * high) / 2; } }
-
测试
public class VisitTest { public static void main(String[] args) { BaseShape baseShape = new BaseShape(4,7,5,10); baseShape.accept(new Visitor()); } }
-
圆形的面积:314.1592653589793
长方形的面积:28.0
正方形的面积:49.0
三角形的面积:17.5
我的理解
访问者设计模式,visitor访问元素,元素提供一个可以接待访问者的接口,访问者实现 Ivisit 类的接口,入参对象是对应的元素 this指向。作为访问者,我要访问谁,需要拿到对应对象的引用,才能访问元素对象;作为元素对象,我要接受访问,就需要提供一个访问接口进行接待。
3.10、中介者
中介者模式 Mediator
3.10.1、定义
菜鸟教程:
中介者模式是用来降低多个对象和类之间的通信复杂性。这中模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护
意图:用一个中介对象类封装一系列对象的交互,中介者使各个对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互
主要解决:对象于对象之间存在大量的关联关系,势必会导致系统结构变得很复杂;同时一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出反应
何时使用:多个类互相关联,形成了网状结构
如何解决:将网状结构分离成星型结构
关键代码:对象Colleague之间的通信封装到一个类中单独处理
注意事项:不应当在指责混乱的时候使用
中介者模式组成角色
- 抽象中介者角色(Mediator):定义同事对象到中介者对象的接口,主要方法是一个或多个事件方法。
- 具体中介者角色(ConcreteMediator):实现了抽象中介者所声明的事件方法。具体中介者知晓所有的具体同事类,并负责具体的协调各同事对象的交互关系。
- 抽象同事类角色(Colleague):定义中介者角色到同事角色之间的接口,同事对象只知道有中介者不知道是否有其它同事。
- 具体同事类角色(ConcreteColleague):具体同事类角色继承抽象同事类,实现自己的业务,在需要与其他同事通信的时候,就与持有的中介者通信,中介者会负责与其他的同事交互。
重构大师:https://refactoringguru.cn/design-patterns/mediator
意图:减少对象之间混乱无序的依赖关系,限制对象之间的直接交互,迫使他们通过中介者对象进行通信
3.10.2、举例
-
抽象中介者
/** * 定义抽象中介者 */ public interface ChatRoom { /** * 发送消息 * @param message * @param user */ void sendMessage(String message,User user); }
-
具体中介者
/** * 具体中介者 */ public class ChatRoomImpl implements ChatRoom{ private List<User> users = new ArrayList<>(); @Override public void sendMessage(String message, User user) { System.out.println("["+user.name+"]发送消息:"+message); for (User u : users) { if (!u.equals(user)){ u.receive(message); } } } public void add(User user){ users.add(user); } }
-
抽象同事
/** * 抽象同事 */ public abstract class User { protected ChatRoom chatRoom; protected String name; public User(ChatRoom chatRoom, String name) { this.chatRoom = chatRoom; this.name = name; } /** * 接收消息 */ public abstract void receive(String message); /** * 发送消息 * @param message */ public abstract void sendMessage(String message); }
-
具体同事
public class Jack extends User{ public Jack(ChatRoom chatRoom, String name) { super(chatRoom, name); } @Override public void receive(String message) { System.out.println("["+name + "] received:"+message); } @Override public void sendMessage(String message) { chatRoom.sendMessage(message,this); } } public class Linda extends User{ public Linda(ChatRoom chatRoom, String name) { super(chatRoom, name); } @Override public void receive(String message) { System.out.println("["+name + "] received:"+message); } @Override public void sendMessage(String message) { chatRoom.sendMessage(message,this); } } public class Lucy extends User{ public Lucy(ChatRoom chatRoom, String name) { super(chatRoom, name); } @Override public void receive(String message) { System.out.println("["+name + "] received:"+message); } @Override public void sendMessage(String message) { chatRoom.sendMessage(message,this); } }
-
测试
public class ChatRoomDemo { public static void main(String[] args) { // 具体中介者 ChatRoomImpl chatRoom = new ChatRoomImpl(); Jack jack = new Jack(chatRoom, "Jack"); Lucy lucy = new Lucy(chatRoom, "Lucy"); Linda linda = new Linda(chatRoom, "Linda"); chatRoom.add(jack); chatRoom.add(lucy); chatRoom.add(linda); jack.sendMessage("当前时间:"+new Date().toLocaleString()+",延迟下班4小时~"); } }
我的理解
中介者设计模式,主要解决对象之间高耦合的问题,把网状的关系,梳理成星型的关系,依靠中介去传递消息。比如租房,房东一没有房,需要房东一电话联系其他房东,然而通过中介,房东与房东之间就没有直接关联。(一个月房租的中介费你受得了吗?)
3.11、解释器(了解)
解释器模式 Interpreter
3.11.1、定义
菜鸟教程
解释器模式提供了评估语言的语法或表达式的方式,这种模式实现了一个表达式接口,该接口解释一个待定的上下文。
意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子
主要解决:对于一些固定文法构建一个解释句子的解释器
何时使用:如果一种特定类型的问题发生的概率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
如何解决:构建语法树,定义终结符与非终结符
注意事项:可以利用场景较少,java中如果碰到可以用expression4j代替
通义千问
解释器设计模式(Interpreter Pattern)是行为设计模式的一种,它定义了一种方法来解释一个语言中的句子。这里的“语言”是指具有特定文法规则的形式语言,而不是自然语言。解释器模式主要用于构建一个解释器,该解释器能够解释并执行这个语言中的句子,从而实现对某种特定领域语言(DSL)的处理。
主要组件
- 抽象表达式(Abstract Expression):定义解释器的接口,声明解释操作的抽象方法。
- 终结符表达式(Terminal Expression):实现抽象表达式的类,代表文法中的终结符,它们可以直接解释成对应的值或动作,不需要进一步分解。
- 非终结符表达式(Non-terminal Expression):同样实现了抽象表达式接口,代表文法中的非终结符,它们通过组合终结符表达式和其他非终结符表达式来构造更复杂的表达式结构。
- 上下文环境(Context):存储解释器可能需要的一些全局信息,比如变量作用域或环境配置等,某些实现中可能不需要这个部分。
- 客户端(Client):构建抽象语法树(AST),使用解释器来解释这些表达式。
适用场景
- 当有一个语言需要解释执行,并且该语言的文法较为简单或领域特定时。
- 需要在运行时动态地处理或解析某种特定格式的输入数据。
- 语言的文法规则可能会频繁变更,且需要能够快速适应这些变更。
优点
- 提供了对语言文法的直接编程表示,易于修改或扩展语言。
- 封装了复杂的文法规则,使得语法的实现和使用分离,提高了可维护性。
缺点
- 对于复杂的文法规则,解释器模式可能会导致类的爆炸性增长,因为每条规则可能都需要一个类来表示。
- 性能可能不是最优,特别是与编译型语言相比,解释执行通常较慢。
- 设计和实现较为复杂,增加了系统的理解难度。
示例
一个常见的解释器模式应用场景是编写一个简单的计算器程序,能够解释并计算数学表达式。这里,表达式中的数字、加减乘除符号等会被解析为终结符表达式,而表达式结构(如加法表达式)则被建模为非终结符表达式,通过组合这些表达式来构建整个表达式的解析树,并最终计算出结果。
表达式引擎:轻量级表达式引擎Expr4j
通义千问,举例
解释器设计模式是一种编程范式,用于构建一个解释器来解释特定语言的语法或规则。这种模式非常适合于实现简单的语言、查询语言、正则表达式解析或者任何可以表达为语法规则的场景。下面通过一个简单的实例来说明解释器设计模式的应用:构建一个算术表达式解释器。
算术表达式解释器实例
假设我们要设计一个能够计算简单算术表达式的解释器,这些表达式只包含非负整数和加减乘除四种运算符。我们可以定义一个抽象表达式类(AbstractExpression)、终结符表达式类(TerminalExpression)和非终结符表达式类(NonterminalExpression)来实现这个解释器。
定义抽象表达式接口
Java
interface Expression { int interpret(); }
实现终结符表达式(数字)
Java
class Number implements Expression { private int number; public Number(int number) { this.number = number; } @Override public int interpret() { return number; } }
实现非终结符表达式(加法和减法)
Java
class AddExpression implements Expression { private Expression left, right; public AddExpression(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret() { return left.interpret() + right.interpret(); } } class SubtractExpression implements Expression { private Expression left, right; public SubtractExpression(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpret() { return left.interpret() - right.interpret(); } }
使用解释器
现在,我们可以使用这些类来构建和解释算术表达式了:
Java
public class InterpreterPatternDemo { public static void main(String[] args) { // 构建表达式 "3 + 4 - 2" Expression expression = new SubtractExpression( new AddExpression(new Number(3), new Number(4)), new Number(2) ); // 计算表达式的值 System.out.println(expression.interpret()); // 输出结果应为 5 } }
在这个例子中,我们定义了一个简单的算术表达式语言,并通过解释器模式实现了对这种语言的解释执行。
Number
类是终结符表达式,直接返回其代表的数值;而AddExpression
和SubtractExpression
是非终结符表达式,它们会递归地解释其子表达式并进行相应的计算。这样,我们就能够灵活地处理和扩展不同的算术表达式了。
3.11.2、举例
模拟实现简单的正则判断:
d : 代表是数字
w : 代表小写字母
W:代表大写字母
A :代表数字+字母
dw :代表数字和小写字母
dW : 代表数字和大写字母
/**
* 抽象表达式
*/
public interface Expression {
boolean express(String str);
}
/**
* 终结表达式
*/
public class TerminalExpression implements Expression{
private String match;
public TerminalExpression(String match) {
this.match = match;
}
@Override
public boolean express(String str) {
switch (match){
case "d":
return str.matches("\\d");
case "w":
return str.matches("[a-z]");
case "W":
return str.matches("[A-Z]");
case "A":
return str.matches("\\w");
case "dw":
return str.matches("[0-9a-z]");
case "dW":
return str.matches("[0-9A-Z]");
}
return false;
}
}
public class OrExpression implements Expression{
private Expression left;
private Expression right;
public OrExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public boolean express(String str) {
for (int i = 0; i < str.length(); i++) {
String s = str.charAt(i) + "";
boolean bl = left.express(s) || right.express(s);
if (bl){
return bl;
}
}
return false;
}
}
public class InterpreterPatternDemo {
public static void main(String[] args) {
AndExpression expression = new AndExpression(new TerminalExpression("W"), new TerminalExpression("d"));
boolean bool = expression.express("AK47");
System.out.println("bool = " + bool);
}
}
我的理解
解释器设计模式,就是内置一套规则。可以实现简单的语言语法,但是设计起来感觉很头疼。各种的表达式引擎,模版引擎,规则引擎,应该都是内置很多规则。
3.12、小结
在spirngboot中的配置文件,通常会有application-dev.yml 和 application-prod.yml配置文件,通过application.yml 中的 profiles->active : dev | prod 决定
选择具体的策略,一般都是通过配置文件,或者后台参数,以及前端传参来决定的。
模板方法,在超类定义了特定的执行逻辑,子类不能重写这个方法,只能按照约定俗成,不能坏了规矩。
最形象的就是死亡流水线了。
观察者与被观察者的关系是,被观察者,是被关注对象【焦点人物】,所以被观察者有啥风吹草动,就需要告诉观察者,这就需要所有的观察者都把自己注册到被观察者身上。所有的观察者,都应该规范的实现统一的接口,这样被观察者进行通知消息可以进行统一调用接口。
迭代器主要用于集合遍历,单独一个类的话,感觉用不上,但是组合模式,进行嵌套,形成树状结构可以使用迭代器。
没有下级的对象,称为叶子节点,非叶子节点有一个容器,存储下级节点。
node.childList.size() > 0 // 等效于 hasNext
遍历 childList
责任链的使用,更像流水线了,从入口到出口。比如过滤器和拦截器。A->B->C->D,过关斩将。
命令模式,通过发送一系列命令,使目标执行特定的逻辑,给机器发送命令。
使用备忘录模式,可以把对象的状态,或者一些属性存储到备忘录里,每一次存储都创建一个新的备忘录对象,然后使用容器把备忘录对象保存进去。
状态模式最好理解就是开关灯了,不管怎么按开关按钮,要么开灯 ,要么关灯。
访问者模式,访问者接口,接收的是被访问的元素;被访问者,提供一个统一接口,接收被访问者,被访问者实现统一接口接收访问者,紧接着在实现里把自己转交给访问者。这样就把容易变的操作和稳定的结构进行解耦合了。
中介者模式,主要解决的是对象之间相互关联,形成网状的关系的结构转变成星型结构。
把可以互相通信,互相依赖的关系,错综复杂,交给中间的媒介,使得耦合变得松散。
解释器,其主要目的是解释,把某种语言解释清楚。java 语言可不识别sql语言,但是通过解释器转化成mysql服务能识别的sql。
解释器也可以理解成翻译吧,但是复杂的语言,需要进行翻译,头大了。
参考知乎:https://zhuanlan.zhihu.com/p/394063958