设计模式
# 23种设计模式

# 创建型模式
# 单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
作用:通过单例模式可以保证系统中,应用该模式的这个类永远只有一个实例。节省内存空间。
**单例在JDK中的体现:**Runtime类、System类、Collections类
分类:
**饿汉式单例:**在类创建时就会创建实例对象
**懒汉式单例:**只有调用getinstance方法时才会创建实例对象
饿汉式又分静态变量饿汉式、静态代码块饿汉式、枚举饿汉式【饿汉式都是线程安全的】
懒汉式又分普通懒汉式【线程不安全】、DCL懒汉式【线程安全】、内部类懒汉式【线程安全】
饿汉式(工作中最常用的)
public class AudioPlayer {
// 私有构造器
private AudioPlayer() {}
// 这个对象是在类加载的时候进行创建的
private static final AudioPlayer instance = new AudioPlayer();
// 提供公共的访问方式
public static AudioPlayer getInstance() {
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
静态代码块方式创建饿汉式单例
public class AudioPlayer {
// 私有构造器
private AudioPlayer() {}
private static final AudioPlayer instance;
static{
instance = new AudioPlayer()
}
// 提供公共的访问方式
public static AudioPlayer getInstance() {
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
枚举饿汉式
public enum VideoPlayer{
INSTANCE;
}
2
3
懒汉式
public class VideoPlayer {
// 1.将构造器私有
private VideoPlayer() {}
// 2.使用成员变量保存创建好的对象
private static VideoPlayer instance;
// 3.提供公共的访问方式
public static VideoPlayer getInstance() {
// 在使用时创建单例对象
if (instance == null) {
instance = new VideoPlayer();
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
**风险:**并发下调用getInstance()方法可能会导致创建多个实例对象
**解决:**在getInstance()方法声明中使用synchronized修饰
**弊端:**导致线程每次调用这个方法时都需要获取锁和释放锁,影响性能
DCL懒汉式(双检锁) DCL:Double Check Locking
public class VideoPlayer {
// 1.将构造器私有
private VideoPlayer() {}
// 2.使用成员变量保存创建好的对象
private static volatile VideoPlayer instance;
// 3.提供公共的访问方式
public static VideoPlayer getInstance() {
// 先进行一次检查是否是第一次创建实例,若已创建则直接返回,减少锁的获取次数
if (instance == null) {
synchronized(VideoPlayer.class){
// 第二次检查是为了防止实例被重复创建
if(instance == null){
// 在使用时创建单例对象
instance = new VideoPlayer();
}
}
}
return instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
为什么要在instance上加volatile修饰?
volatile关键字能够保证线程的可见性和有序性,这里主要是用于保证线程的有序性;首先需要知道instance = new VideoPlayer();这样一句代码在CPU中其实分为几个步骤:
- 创建对象(分配内存空间)
- 调用构造方法(初始化成员变量)
- 给静态变量赋值
然后CPU会对我们的代码执行顺序进行一个优化,对于没有因果先后关系的代码,可能会改变执行顺序,比如在这段代码中,调用构造方法和静态变量赋值就没有因果关系,这两个的执行顺序就有可能被颠倒。因此在多线程的环境下就有可能会出现以下的执行顺序:(注:橙蓝代表不同线程)
导致多线程的情况下出现创建对象还未初始化,这个对象就被返回了的问题,最终拿到的对象属性不完整,从而引发更多奇奇怪怪的错误。
而被volatile修饰的变量就会在该变量的赋值语句之后产生一个内存屏障,规定在这条语句之前的赋值语句不能越过屏障执行,从而保证了线程的有序性,防止CPU在进行创建对象分配内存空间及初始化对象,也就是调用构造方法时出现乱序。
懒汉式 - 静态内部类(推荐)
public class VideoPlayer {
// 将构造器私有
private VideoPlayer() {}
// 创建内部类
private static class Holder {
static VideoPlayer instance = new VideoPlayer();
}
// 提供公共的访问方式
public static VideoPlayer getInstance() {
// 创建单例
return Holder.instance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
从饿汉式可以看出,类加载时初始化静态成员变量是没有线程安全问题的,所以可以使用内部类的方式创建实例
**小贴士:**静态内部类和非静态内部类一样,都不会因为外部内的加载而加载,同时静态内部类的加载不需要依附外部类,在使用时才加载,不过在加载静态内部类的过程中也会加载外部类
# 工厂模式
工厂模式可以分为三类:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
简单工厂其实不是一个标准的的设计模式。GOF 23 种设计模式中只有「工厂方法模式」与「抽象工厂模式」。简单工厂模式可以看为工厂方法模式的一种特例,为了统一整理学习,就都归为工厂模式。
工厂模式是创建型模式中比较重要的。工厂模式的主要功能就是帮助我们实例化对象。之所以名字中包含工厂模式四个字,是因为对象的实例化过程是通过工厂实现的,是用工厂代替 new 操作的。
工厂模式优点
- **可以使代码结构清晰,有效地封装变化。**在编程中,产品类的实例化有时候是比较复杂和多变的,通过工厂模式,将产品的实例化封装起来,使得调用者根本无需关心产品的实例化过程,只需依赖工厂即可得到自己想要的产品。
- **对调用者屏蔽具体的产品类。**如果使用工厂模式,调用者只关心产品的接口就可以了,至于具体的实现,调用者根本无需关心。即使变更了具体的实现,对调用者来说没有任何影响。
- **降低耦合度。**产品类的实例化通常来说是很复杂的,它需要依赖很多的类,而这些类对于调用者来说根本无需知道,如果使用了工厂方法,我们需要做的仅仅是实例化好产品类,然后交给调用者使用。对调用者来说,产品所依赖的类都是透明的。
适用场景
不管是简单工厂模式,工厂方法模式还是抽象工厂模式,他们具有类似的特性,所以他们的适用场景也是类似的。
- 首先,作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
- 其次,工厂模式是一种典型的解耦模式,迪米特法则在工厂模式中表现的尤为明显。假如调用者自己组装产品需要增加依赖关系时,可以考虑使用工厂模式。将会大大降低对象之间的耦合度。
- 再次,由于工厂模式是依靠抽象架构的,它把实例化产品的任务交由实现类完成,扩展性比较好。也就是说,当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。
# 简单工厂模式
简单工厂模式的角色如下:
| 角色 | 解释 |
|---|---|
| 简单工厂SimpleFactory | 负责创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。 |
| 抽象产品IProduct | 简单工厂创建的多有对象的父类,负责描述所有实例共有的公共接口。 |
| 具体产品ConcreteProduct | 简单共产模式的创建目标 |
简单示例:
public interface IProuduct {
void doSomeThing();
}
2
3
public class ProductA implements IProuduct {
@Override
public void doSomeThing() {
System.out.println("我是ProductA");
}
}
2
3
4
5
6
public class ProductB implements IProuduct {
@Override
public void doSomeThing() {
System.out.println("我是ProductB");
}
}
2
3
4
5
6
public class SimpleFactory {
// 通过传入的productName来决定生成哪个具体产品
static IProuduct makeProduct(String productName) {
if ("ProductA".equals(productName)) {
return new ProductA();
} else if ("ProductB".equals(productName)) {
return new ProductB();
} else {
return null;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
// 生成产品B
IProuduct product = SimpleFactory.makeProduct("ProductB");
product.doSomeThing();
}
}
2
3
4
5
6
7
我是ProductB
简单工厂模式总结
**优点:**简单工厂模式,封装了创建对象的逻辑,完成了创建对象逻辑与业务代码逻辑的解耦。试想客户端是多个service层的文件,如果使用new创建对象,当我们要统一改变产生对象的逻辑时,需要在多个文件中找到这部分代码进行修改。在使用简单工厂模式后,只需要修改简单工厂中生成对象的逻辑即可,不需要修改业务代码。完成了解耦。
**缺点:**每当具体产品类的抽象产品类增多时,会需要在简单工厂类中新增关于新增产品类对象生成的方法。当抽象产品类很多时,抽象工厂会很臃肿。并且在这种情形下,SimpleFactory类也不符合开闭原则。
# 工厂方法模式
定义创建对象的接口,但具体实现放到实现这个接口的管理具体一类产品的对象生成的工厂类中去实现。
工厂方法模式的角色
| 角色 | 解释 |
|---|---|
| 抽象工厂 | 定义了创建抽象产品的方法,任何在模式中创建对象的工厂类都必须实现这个接口 |
| 具体工厂 | 实现抽象工厂接口的具体工厂类,负责生产具体的产品 |
| 抽象产品 | 工厂方法模式所创建的对象的超类型。也是产品对象的共同父类或者共同拥有的接口 |
| 具体产品 | 实现了抽象产品角色所定义的接口。某具体产品由具体工厂创建,他们往往一一对应 |
简单示例:
/**
* 抽象工厂
*/
public interface IFactory {
IProduct makeProduct();
}
2
3
4
5
6
/**
* 抽象产品
*/
public interface IProduct {
void doSomeThing();
}
2
3
4
5
6
public class ProductA implements IProduct {
@Override
public void doSomeThing() {
System.out.println("我是ProductA");
}
}
2
3
4
5
6
public class ProductB implements IProduct {
@Override
public void doSomeThing() {
System.out.println("我是ProductB");
}
}
2
3
4
5
6
/**
* 生产ProductA的具体工厂
*/
public class FactoryA implements IFactory {
@Override
public IProduct makeProduct() {
return new ProductA();
}
}
2
3
4
5
6
7
8
9
/**
* 生产ProductB的具体工厂
*/
public class FactoryB implements IFactory {
@Override
public IProduct makeProduct() {
return new ProductB();
}
}
2
3
4
5
6
7
8
9
public class Client {
public static void main(String[] arges) {
// 生产ProductA
FactoryA factoryA = new FactoryA();
factoryA.makeProduct().doSomeThing();
}
}
2
3
4
5
6
7
我是ProductA
Mybatis示例:
/**
* Builds {@link SqlSession} instances.
*
* @author Clinton Begin
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
在不与 Spring 集成的情况下,使用 MyBatis 执行数据库的操作主要如下:
InputStream is = Resources.getResourceAsStream("myBatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
sqlSession = factory.openSession();
2
3
4
- 加载配置文件,组装Configuration对象
- 创建工厂Builder获取会话工厂实例
- 与数据库建立连接,开启会话
首先在这里,我们并不用关心数据库连接池的实现是什么,因为抽象行为是相同的,只管获取会话工厂对象创建会话实例,然后如果我们想修改实现的话,只需要在配置文件中修改驱动和方言即可,不需要修改业务代码,做到了解耦。
工厂方法模式适用场景
工厂方法模式和简单工厂模式虽然都是通过工厂来创建对象,他们之间最大的不同是——工厂方法模式在设计上完全完全符合“开闭原则”。
在以下情况下可以使用工厂方法模式:
- 不需要知道具体实现类型时:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
- 所有子类抽象行为相同时:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
- 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
使用场景
- 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
- 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。
- 设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
- 比如 Mybatis 换数据库只需换方言和驱动就可以
小结
从简单工厂模式的讲述知道:简单工厂的一个缺点在于,每当需要新增产品时,都需要修改负责生产产品的SimpleFactory类,违背了“开闭原则”,并且会使SimpleFactory类十分的臃肿。而使用工厂方法模式后,当新增ProductC时,只需要对应创建具体产品类ProductC和负责生产ProductC的具体工厂FactoryC即可。符合“开闭原则”,便于扩展。
它的缺点在于:
(1)类的个数容易过多,增加复杂度
(2)实现抽象工厂接口的具体工厂只能生产出一种产品(可以用抽象工厂模式解决)
# 抽象工厂模式
抽象工厂模式是在工厂方法模式的基础上进行进一步抽象,抽象工厂里的角色与工厂方法模式如出一辙:
| 角色 | 说明 |
|---|---|
| 抽象工厂角色 | 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的 父类。在java中它由抽象类或者接口来实现 |
| 具体工厂角色 | 它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。在java中它 由具体的类来实现 |
| 抽象产品角色 | 它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现 |
| 具体产品角色 | 具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现 |
工厂方法模式,创建了很多具体工厂类,而没有利用产品的“商品族”的概念,而抽象工厂模式就是用于解决“一类产品”的创建问题。
比如:
- 汽车有:商务型、轿跑型、MINI型等等
- 汽车的品牌有:宝马、奔驰、奥迪
直接上UML图:
抽象工厂模式的优缺点
- **优点:**抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展
- **缺点:**抽象工厂模式很难支持新种类产品的变化。这是因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变,这就违背了"开闭原则"。
适用场景
使用抽象工厂模式的系统应该符合以下几个条件:
- 一个系统不要求依赖产品实例如何被创建、组合和表达
- 这个系统有多个系列产品,而系统中只消费其中某一系列产品
- 系统要求提供一个产品类的库,所有产品以同样的接口出现,客户端不需要依赖具体实现
参考:
JAVA设计模式之工厂模式(三种工厂模式)_工厂模式java_随逸星的博客-CSDN博客 (opens new window)
# 行为型模式
# 策略模式
策略模式就是利用各种策略类来替代冗余的if else代码,使得代码整洁,一定程度上提高了性能,并且新增策略场景不需要修改原代码,只需要添加一个策略类。让策略类都实现一个共同的策略接口,从而实现不同策略类的不同策略实现,然后将策略类都存放在一个策略执行类中,根据不同类型执行不同策略。
策略模式优缺点
1)优点
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不 修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法族的办法。
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
2)缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类
- 策略模式会增加对象的创建,可以通过使用享元模式在一定程度上减少对象的数量。
简单应用示例:需求:商品折扣活动,存在多种客户,不同客户享用的折扣不同
创建策略类
public class CommonDiscount implements DiscountStrategy {
/**
* 计算折扣:普通用户8折
* @param price 价格
* @param total 折扣
* @return 折后价格
*/
@Override
public double calcPrice(double price, int total) {
return price * total * 0.8;
}
}
2
3
4
5
6
7
8
9
10
11
12
public class VIPDiscount implements DiscountStrategy{
/**
* 计算折扣:VIP用户5折
* @param price 价格
* @param total 数量
* @return 折后价格
*/
@Override
public double calcPrice(double price, int total) {
return price * total * 0.5;
}
}
2
3
4
5
6
7
8
9
10
11
12
创建策略执行类
public class DiscountHandler {
private final DiscountStrategy discountStrategy;
// 注入
public DiscountHandler(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
// 执行策略
public double getDiscountPrice(double price, int total){
return discountStrategy.calcPrice(price, total);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
测试类
public class DemoClass {
public static void main(String[] args) throws IllegalAccessException {
System.out.println("普通客户:" + new DiscountHandler(new CommonDiscount()).getDiscountPrice(100, 1));
System.out.println("VIP客户:" + new DiscountHandler(new VIPDiscount()).getDiscountPrice(100, 1));
}
}
2
3
4
5
6
7
8
普通客户:80.0
VIP客户:50.0
2
企业应用场景示例:
这个项目的一个消息分发架构采用的就是策略模式,这是基于EMQ的一个消息分发架构,我觉得它同样适用与kafka。大致的一个架构是这样的,最核心的两个部分是消息处理器加载器和消息分发处理器。
消息处理器加载器就如其名,加载消息处理器,也就是策略类,这里我们自定义了一个注解@Topic注解用于存放主题名,在定义消息处理器的时候加上这个注解也就代表着这个消息处理器订阅的主题,然后消息处理器加载器实现了ApplicationContextAware接口,在服务器启动的时候会自动调用里面的setApplicationContext()方法,通过这个方法获取应用上下文对象,然后通过策略类接口类型获取容器中所有的策略类,也就是容器中所有的消息处理器,然后以@Topic上的主题名为key,对应的消息处理器对象为value,存到一个Map里面方便后续获取。
消息分发器呢,由两个核心类组成:
一个就是消息处理类,用来编写调用策略类方法的逻辑,就是通过主题名去存放消息处理器对象的map中获取对应的消息处理器,然后调用其业务方法。
另一个则是EMQ的一个回调类,通过实现MqttCallbackExtended接口,实现里面的messageArrived()方法和connectComplete()方法,messageArrived会在接收到消息的时候调用,也就是在这里传入主题和接收到消息,调用消息处理类的消息处理方法。而connectComplete则是在EMQ连接成功时触发,这个方法的逻辑则是读取配置文件中配置的主题列表,遍历订阅这些主题,也就是主题的一个自动订阅。
这样一个全自动又优雅的消息分发架构就做好了,我们需要做的,只需要定义消息处理器,实现消息处理器接口,加上@Topic注解订阅主题就可以了。
# 观察者模式
观察者模式又称发布/订阅模式,用于定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
俩个重要的组成部分:
- subject主题接口,用于描述订阅主题、取消订阅、通知订阅者的行为
- Observer观察/订阅者接口,用于描述监听到主题更新后进行的行为
观察者模式特点
优点:
- 一个观察目标可以对应多个观察者,而这些观察者之间没有相互联系,所以能够根据需要增加和删除观察者,使得系统更易于扩展,符合开闭原则;
- 并且观察者模式让目标对象和观察者松耦合,虽然彼此不清楚对方的细节,但依然可以交互,目标对象只知道一个具体的观察者列表,但并不认识任何一个具体的观察者,它只知道他们都有一个共同的接口。
缺点:
- 如果存在很多个被观察者的话,那么将需要花费一定时间通知所有的观察者,如果观察者与被观察者之间存在循环依赖的话,那么可能导致系统崩溃,
- 观察者模式没有相应的机制让观察者知道被观察对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
简单应用示例
需求:气候变化,通知所有在监听该气候的气象局
主题接口
public interface Subject {
/**
* 注册观察者(订阅主题)
* @param o 观察者
*/
void registerObserver(Observer o);
/**
* 注销观察者(取消订阅)
* @param o 观察者
*/
void removeObserver(Observer o);
/**
* 通知订阅者
*/
void notifyObservers();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
观察/订阅者接口
public interface Observer {
/**
* 更新数据
* @param temperature 气温
* @param pressure 气压
* @param humidity 湿度
*/
void updateData(float temperature, float pressure, float humidity);
}
2
3
4
5
6
7
8
9
10
11
气候主题(核心类)
public class WeatherData implements Subject {
/**
* 气温
*/
private float temperatrue;
/**
* 气压
*/
private float pressure;
/**
* 湿度
*/
private float humidity;
/**
* 订阅者集合
*/
private ArrayList<Observer> observers = new ArrayList<>();
/**
* 注册订阅者(订阅主题)
* @param o 观察者
*/
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
/**
* 注销订阅者(取消订阅)
* @param o 观察者
*/
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
/**
* 通知订阅者
*/
@Override
public void notifyObservers() {
// 遍历订阅者进行通知
for (Observer observer : observers) {
observer.updateData(temperatrue, pressure, humidity);
}
}
/**
* 加载数据
* @param temperature 气温
* @param pressure 气压
* @param humidity 湿度
*/
public void setWeatherData(float temperature, float pressure, float humidity){
this.temperatrue = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 通知订阅当前主题的观察者
notifyObservers();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
NASA气象局
public class NASAObserver implements Observer{
/**
* 更新数据
* @param temperature 气温
* @param pressure 气压
* @param humidity 湿度
*/
@Override
public void updateData(float temperature, float pressure, float humidity) {
System.out.println("NASA气象局更新最新天气数据:" + "\n"
+ "气候:" + temperature + "\n"
+ "气压:" + pressure + "\n"
+ "湿度:" + humidity + "\n"
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
中央气象局
public class CenterObserver implements Observer{
/**
* 更新数据
* @param temperature 气温
* @param pressure 气压
* @param humidity 湿度
*/
@Override
public void updateData(float temperature, float pressure, float humidity) {
System.out.println("中央气象局更新最新天气数据:" + "\n"
+ "气候:" + temperature + "\n"
+ "气压:" + pressure + "\n"
+ "湿度:" + humidity + "\n"
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
测试类
public class DemoClass {
public static void main(String[] args){
// 创建天气主题
WeatherData weatherData = new WeatherData();
// 两个气象局订阅天气主题
weatherData.registerObserver(new CenterObserver());
NASAObserver nasaObserver = new NASAObserver();
weatherData.registerObserver(nasaObserver);
// 加载天气数据
weatherData.setWeatherData(24, 100f, 18.5f);
// NASA取消订阅
weatherData.removeObserver(nasaObserver);
System.out.println("----------------气候变化----------------");
// 天气数据修改
weatherData.setWeatherData(15, 33.3f, 18.5f);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
中央气象局更新最新天气数据:
气候:24.0
气压:100.0
湿度:18.5
NASA气象局更新最新天气数据:
气候:24.0
气压:100.0
湿度:18.5
----------------气候变化----------------
中央气象局更新最新天气数据:
气候:15.0
气压:33.3
湿度:18.5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 备忘录模式&命令模式
由于在企业开发中,备忘录模式一般与命令模式一起使用,这里一起进行赘述。
# 备忘录模式
备忘录模式所涉及的角色有三个:
- **备忘录(Memento)角色:**备忘录对象(存档),负责保存好记录,即Originator内部状态
- **发起人(Originator)角色:**对象(需要保存状态的对象)
- **负责人(Caretaker)角色:**守护者对象(存档列表),负责保存多个备忘录对象,使用集合管理,提高效率
备忘录模式的注意事项和细节
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
- 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存,这个需要注意。
- 适用的应用场景:1、后悔药;2、打游戏时的存档;3、Windows里的ctrl+z;4、IE中的后退;5、数据库的事务管理(rollback)。
# 命令模式
命令模式所涉及的角色有三个:
- **Receiver:**是接受者,实际执行命令的对象
- **Command:**是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
- **Invoker :**是调用者角色,存储命令和调用命令的对象
命令模式的注意事项和细节
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要 调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对 象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:” 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到 了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这 点在在使用的时候要注意
模拟一个jdbc事务回滚的情况:
先来描述一下基本步骤,首先我们在业务层输入增删改查命令,由sqlSession调用Statement转义Sql并执行,出现报错,则回滚到事务开启时的状态。
首先命令模式的角色
public interface Command {
// 执行命令
void execute();
}
2
3
4
public class SaveData implements Command{
private final Statment statment;
public SaveData(List<String> table, String data) {
this.statment = new Statment(table, data);
}
@Override
public void execute() {
statment.save();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
public class DeleteData implements Command{
private final Statment statment;
public DeleteData(List<String> table, String data) {
this.statment = new Statment(table, data);
}
@Override
public void execute() {
statment.delete();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
命令接收者是Statement
public class Statement {
private List<String> table;
private String data;
public Statement(List<String> table, String data) {
this.table = table;
this.data = data;
}
// 增
public boolean save(){
System.out.println("添加了:" + data);
return table.add(data);
}
// 删
public boolean delete(){
System.out.println("删除了:" + data);
return table.remove(data);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
调用者SqlSession
public class SqlSession {
private final List<Command> sqlList;
private SqlSession() {
sqlList = new ArrayList<>();
}
public static SqlSession opSession(){
return new SqlSession();
}
public void addSqlCommand(Command ...sqlCommand){
sqlList.addAll(Arrays.asList(sqlCommand));
}
public void executeSql(){
System.out.println("正在执行SQL...");
for (Command command : sqlList) {
command.execute();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
然后是备忘录模式角色
// 模拟数据库表(Originator 需要保存的对象)
List<String> table = new ArrayList<>();
2
public class Translation {
private List<String> table;
// 开启事务
public void openTranslation(List<String> table) {
this.table = new ArrayList<>(table);
}
// 回滚
public List<String> rollback() {
return this.table;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TranslationManager {
private final List<Translation> translationList = new ArrayList<>();
public Translation getTranslation(int index) {
return translationList.get(index);
}
public void addTranslation(Translation translationList) {
this.translationList.add(translationList);
}
}
2
3
4
5
6
7
8
9
10
11
12
测试类
/**
* 模拟一下数据库增删回滚的操作
*/
public static void main(String[] args){
// 模拟数据库表(Originator 需要保存的对象)
List<String> table = new ArrayList<>();
// 创建事务管理者
TranslationManager translationManager = new TranslationManager();
// 开启数据库会话
SqlSession sqlSession1 = SqlSession.opSession();
// 查看表
System.out.println("当前表数据:" + table + "\n");
// 创建子事务1
System.out.println("开启事务1...");
Translation translation1 = new Translation();
translation1.openTranslation(table);
// 加入总事务管理
translationManager.addTranslation(translation1);
// 编写sql
Command sql1 = new SaveData(table, "张三");
Command sql2 = new SaveData(table, "李四");
// 添加sql到会话中
sqlSession1.addSqlCommand(sql1, sql2);
// 执行事务1sql
sqlSession1.executeSql();
// 开启数据库会话
SqlSession sqlSession2 = SqlSession.opSession();
// 查看表
System.out.println("当前表数据:" + table + "\n");
// 创建子事务2
System.out.println("开启事务2...");
Translation translation2 = new Translation();
translation2.openTranslation(table);
// 加入总事务管理
translationManager.addTranslation(translation2);
// 编写sql
Command sql3 = new DeleteData(table, "张三");
// 添加sql到会话中
sqlSession2.addSqlCommand(sql3);
try {
// 执行sql
sqlSession2.executeSql();
System.out.println("当前表数据:" + table + "\n");
// 模拟执行删除操作时报错
throw new Exception();
} catch (Exception e) {
System.out.println("回滚至事务2开启时");
// 回滚到事务2的时候
table = translationManager.getTranslation(1).rollback();
System.out.println("当前表数据:" + table + "\n");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
当前表数据:[]
开启事务1...
正在执行SQL...
添加了:张三
添加了:李四
当前表数据:[张三, 李四]
开启事务2...
正在执行SQL...
删除了:张三
当前表数据:[李四]
回滚至事务2开启时
当前表数据:[张三, 李四]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 结构型模式
# 适配器模式
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,用于结合两个独立接口的功能,它允许将一个类的接口转换成另一个接口。这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。
适配器模式的角色
| 角色 | 解释 |
|---|---|
| Target | 目标接口/该接口的实现类。 |
| Adapter | 适配器类,实现了 Target 接口,并且包含一个 Adaptee 类型的对象。 |
| Adaptee | 需要适配的接口/该接口的实现类。 |
举一个非常贴切的例子:switch连接显示器
- 显示器(Target):支持HDMI接口
- switch(Adaptee):支持TypeC接口
- switch的充电底座(Adapter):支持TypeC转HDMI
首先是两个接口
public interface HDMI {
// 图像输出
void show();
}
2
3
4
public interface TypeC {
// 充电
void charge();
}
2
3
4
然后是两个接口的实现类
public class Displayer {
public void connent(HDMI hdmi){
hdmi.show();
}
}
2
3
4
5
public class Switch implements TypeC{
public void play(){
System.out.println("正在游玩塞尔达传说...");
}
@Override
public void charge() {
System.out.println("正在充电...");
}
}
2
3
4
5
6
7
8
9
10
适配器
public class NSChargingBase implements HDMI{
private Switch ns;
public void connect(TypeC typeC){
ns = (Switch) typeC;
ns.charge();
}
@Override
public void show() {
ns.play();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试类
public class DemoClass {
public static void main(String[] args){
Displayer displayer = new Displayer();// 显示器
Switch ns = new Switch();// switch
NSChargingBase nsChargingBase = new NSChargingBase();// 底座
// 将ns插入底座
nsChargingBase.connect(ns);
// 显示器连接底座
displayer.connent(nsChargingBase);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
正在充电...
正在游玩塞尔达传说...
2
当然适配器接入适配接口的方式有很多种,可以继承,也可以通过构造方法注入
除此之外,JDK中的InputStreamReader和OutputStreamWriter运用的就是适配器模式
InputStream fis = new FileInputStream("Ex_Test\\TestFile\\a.txt");
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
2
3
转换流可以充当字节流转换字符流的中介
适配器模式的特点和运用
对象适配器的优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据’里氏替换原则’适配者的子类也可以通过该适配器进行适配
类适配器的缺点 :
- 对于java,c#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不同同时适配多个适配者
- 在java,c#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性
使用场景:
- 系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码
- 像创建一个重复使用的类,用于与一些彼此之间没有太大联系的类,包括一些可能子啊将来一起工作的类
- 比如GUI编程,springboot
# 装饰模式
定义:动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。
作用:可以对原本的类进行增强和扩展功能
**装饰模式有3个角色:**① 原对象 ② 装饰对象 ③ 共同的接口
- 原对象与装饰对象实现同个接口,装饰对象通过接收原对象来对原对象的功能进行增强扩展
Java中装饰模式的体现:IO流、缓冲流、对象流
示例:
接口
public interface PowerUp {
void Link();
void scope();
}
2
3
4
原对象
public class WiFi implements PowerUp{
private int link_count = 3;
private int link_scope = 100;
@Override
public void Link() {
System.out.println("最大链接个数:"+link_count+" 个");
}
@Override
public void scope() {
System.out.println("WiFi范围:"+link_scope+" m");
}
public int getLink_count() {
return link_count;
}
public int getLink_scope() {
return link_scope;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
装饰对象
public class SuperWiFi implements PowerUp{
WiFi wiFi = new WiFi();
// 接收原对象进行加强
public SuperWiFi(WiFi wiFi) {
this.wiFi = wiFi;
}
// 加强
@Override
public void Link() {
System.out.println("加强link功能");
int up_link_count = wiFi.getLink_count()+10;
System.out.println("最大链接个数:"+up_link_count+" 个");
}
@Override
public void scope() {
System.out.println("加强scope功能");
int up_link_scope = wiFi.getLink_scope()+100;
System.out.println("WiFi范围:"+up_link_scope+" m");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
测试类
public class Testcode {
public static void main(String[] args){
SuperWiFi superWiFi = new SuperWiFi(new WiFi());
superWiFi.Link();
superWiFi.scope();
}
}
2
3
4
5
6
7
8
9
加强link功能
最大链接个数:13 个
加强scope功能
WiFi范围:200 m
2
3
4