工作中常用的设计模式
# 工作中常用的设计模式
# 策略模式
# 定义
策略模式(Strategy Pattern)是行为型策略模式当中的一种,定义一系列算法,将每个算法封装到具有公共接口的一系列策略类中,(可以是抽象类,也可以是接口),从而使他们可以相互替换,让算法可在不影响客户端的情况下发生变化。
作用:将算法的责任和本身进行解耦,使得:
- 算法可独立于使用外部而变化
- 客户端方便根据外部条件选择不同的策略来解决不同的问题
简单的说:就是面对不同的情况采取不同的解决办法,但每个解决办法是在独立的类中而不是在一起。
开发过程中遇到同一问题的不同参数时,我们需要采取不同的应对措施(不同的业务代码)。通常来说,如果参数种类少,我们通常采用if-else的方法来解决。
但如果参数种类过多,仍采用if-else就会导致一个类过于的臃肿且后期不易维护,此时就可以采用策略模式。不同参数的解决方法写在不同的实现类中,这样的好处就是后期容易维护且对先前的代码影响微乎其微。
# 例子
// 策略接口
public interface Strategy {
int doOperation(int num1, int num2);
}
// 有两个不同的实现
public class OperationAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class OperationSubtract implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
// Context 类
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
// 测试类
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new OperationAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context = new Context(new OperationSubtract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
}
}
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
# 组成
从上述例子中我们可以发现,策略模式要实现,至少需要3类角色:
- 抽象策略类:抽象角色,通常由一个接口或抽象类实现。该角色给出所有的具体策略类所需的接口。
- 具体策略类:实现了抽象策略定义的接口,提供了具体的算法实现或行为。
- 执行类或环境类:持有一个策略类的引用,最终给客户端调用。
# 优缺点
它解决了扩展跟维护的问题,但美中不足的是造成了策略实现类过多。因此若条件不多时,仍推荐使用if-else。
# 责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它用于将请求的发送者和接收者解耦,使得多个对象都有机会处理这个请求。
# 定义
责任链模式定义了一个请求处理对象的链条,每个对象都可以处理请求或者将请求转发给下一个对象,直到有一个对象处理请求为止。在责任链模式中,请求发送者不需要知道链条中具体的处理对象,只需要将请求发送给链头即可,具体的处理过程和实现细节由链条中的对象来决定。
它的优点在于:有效地解耦请求发送者和接收者,分离职责,提高系统的灵活性和可维护性。同时可以动态地增加、删除或修改节点。
常用于:登录验证、权限校验、日志记录、异常处理等。
异常不要用来做流程控制、条件控制。因为异常的设计初衷是解决程序运行中的各种意外情况,而且异常的处理效率比条件判断要低得多。
# 组成
- Handler(处理者):定义了处理请求的接口,通常包含一个抽象方法或者一个处理请求的抽象类。每个处理者都知道自己的后继者(下一个处理者),如果自己不能处理该请求,则将其转发给后继者。
- ConcreteHandler(具体处理者):实现了 Handler 接口,并对请求进行实际处理。每个具体处理者都能够处理一些特定的请求类型,如果自己不能处理该请求,则将其转发给后继者。
- Client(客户端):创建链条的起点,向链条头部的处理者发送请求。
public abstract class Handler {
private Handler successor; // 后继节点
public void setSuccessor(Handler successor) {
this.successor = successor;
}
// 处理请求的抽象方法
public abstract void handleRequest(Request request);
protected void next(Request request) {
if (successor != null) { // 如果有后继节点,则转发请求
successor.handleRequest(request);
}
}
}
public class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(Request request) {
if (canHandle(request)) { // 判断是否能够处理该请求
// 处理请求
} else {
next(request); // 转发请求给下一个处理者
}
}
private boolean canHandle(Request request) {
// 判断是否能够处理该请求的逻辑
}
}
public class Client {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.setSuccessor(handlerB); // 设置后继节点
Request request = new Request();
handlerA.handleRequest(request); // 发送请求到链头
}
}
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
# 应用
责任链模式在Java中有许多应用场景,例如:
- 过滤器(Filter):在Servlet中,过滤器就是使用责任链模式实现的。每个过滤器都可以决定是否处理请求,或者将其转发给下一个过滤器进行处理。
- 拦截器(Interceptor):在Spring框架中,拦截器就是使用责任链模式实现的。拦截器可以对请求进行预处理或后处理,也可以将请求转发给下一个拦截器进行处理。
- 异常处理(Exception Handling):在Java中,可以使用责任链模式来处理异常。首先,程序先尝试使用自定义的异常处理器来处理异常,如果该处理器无法处理异常,则将其转发给下一个处理器进行处理。
- 日志记录(Logger):在Java中,可以使用责任链模式来记录日志。每个日志记录器都可以决定是否需要记录该日志,或者将其转发给下一个日志记录器进行记录。
# 优缺点
- 增加了给对象添加责任的灵活性,请求的处理节点可以灵活的增加或删减
- 减少了对象之间的耦合,每个对象只需要知道下一个对象的接口
- 缺:责任链可能会非常长,影响性能。
- 缺:可能会有循环引用的问题,导致栈溢出。
# 观察者模式
# 定义
观察者模式(Observer Pattern)是行为设计模式之一,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象。这个主题对象(被观察者)在状态发生变化时,会通知所有观察者对象,使它们(观察者)能够自动更新自己。
如此说来,观察者模式只用2类角色就可以实现:观察者和被观察者。那我们在被观察者状态改变的方法中,直接调用观察者对应的方法,即为实现了观察者模式。
但注意:上述的实现确实成功了,但它却使观察者和被观察者对象之间的紧密的耦合起来,从根本上违反面向对象的设计的原则——开闭原则。
优雅的方式应该是:观察者与被观察者对象之间的互动关系不应该是类之间的直接调用,通过抽象类的方式进行实现。
# 组成
为了实现观察者模式同时实现观察者跟被观察者之间的低耦合,它应该有这4类角色:
- 抽象主题(被观察者):它是具有状态的对象,并维护着一个观察者列表,并会在主题内部状态发生改变时,给所有登记的观察者发出通知。主题提供了添加、删除和通知观察者的方法。
- 具体主题:它是抽象主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
- 抽象观察者:它是接收主题通知的对象。观察者应该有一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
- 具体观察者:它是抽象观察者的实现类,实现了更新方法,定义了在收到主题通知时需要执行的具体操作。
# 示例
// 观察者接口
interface Observer {
void update(float temp, float humidity, float pressure);
}
// 主题接口
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 具体的主题实现
class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
// 当获取新的测量数据时,通知观察者
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
// 具体观察者实现
class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
}
}
// 使用观察者模式的示例
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
weatherData.registerObserver(currentDisplay);
// 模拟新的气象测量数据
weatherData.setMeasurements(30, 65, 30.4f);
}
}
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
在这个例子中,WeatherData
类实现了Subject
接口,负责存储气象数据,并在数据更新时通知观察者。CurrentConditionsDisplay
类实现了Observer
接口,用于显示当前的气象状况。当WeatherData
的气象数据更新时,它会通知所有注册的观察者,而观察者则会使用新的数据更新自己的状态。
# 应用场景
- 事件处理:观察者对象订阅特定事件,并在事件发生时接收通知并执行相应的操作。
- 数据更新通知:消息通知功能的实现,比如表单注册后,发送一系列对应信息
- 数据分析:在实时数据分析系统中,用于监听数据源的变化,并实时更新视图。
- 日志记录:观察者模式可以用于实时日志记录系统,其中日志记录器充当被观察者,而观察者可以是日志分析器、报警系统等。当日志发生变化时,观察者将收到通知并执行相应的操作,如生成报告、发送警报等。
# 优缺点
优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率 2、目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
# 扩展
观察者(Observer)模式是比较常用的设计模式之一,但因为这个设计模式实在太常用了,Java就把它放到了JDK里面。但更新较慢,现在如果我们想使用观察者模式,我推荐Guava的EventBus
,它是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现。对于事件监听和发布订阅模式,EventBus是一个非常优雅和简单解决方案,我们不用创建复杂的类和接口层次结构。
# demo
public class Event {
private int munber;
public Event(int munber){
this.munber = munber;
System.out.println("event message:"+munber);
}
public int getMunber(){
return munber;
}
}
import com.google.common.eventbus.Subscribe;
public class EventListener {
public int lastMessage = 0;
@Subscribe
public void listen(Event event) {
lastMessage = event.getMunber();
System.out.println("监听者-Message:"+lastMessage);
}
public int getLastMessage() {
return lastMessage;
}
}
//测试 实验
public class TestEventBus {
public static void main(String[] args) {
EventBus eventBus = new EventBus("test");
EventListener eventListener = new EventListener();
eventBus.register(eventListener);
System.out.println(eventListener.getLastMessage());
eventBus.post(new Event(100));
eventBus.post(new Event(200));
eventBus.post(new Event(300));
System.out.println(eventListener.getLastMessage());
}
}
// 输出结果如下
0
event message:100
监听者-Message:100
event message:200
监听者-Message:200
event message:300
监听者-Message:300
300
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
在上述demo中,我们在方法listen
使用@Subscribe
注解,之后再创建总线,将监听者注册,一旦事件发生变化,对应方法便会执行。
小结:使用Guava后,只需要在指定的方法上加上@Subscribe
注解即可。当然,如果要多个方法都起到监听,则每个方法都加上@Subscribe
注解即可。
关于Guava中的EventBus还有一些这里就不展开了。此外关于观察者,上述我们说的消息都是监听者被推送消息的方式,其实还有主动拉去消息的方式。
此外推送的消息,还分同步跟异步。
同步执行,事件发送方在发出事件之后,会等待所有的事件消费方执行完毕后,才会回来继续执行自己后面的代码。
异步执行,事件发送方异步发出事件,不会等待事件消费方是否收到,直接执行自己后面的代码。
https://www.cnblogs.com/guanbin-529/p/13022610.html
# 工厂模式
工厂模式通常有三种类型:简单工厂(静态工厂)、工厂方法模式以及抽象工厂模式
# 定义
- 简单工厂:它通过一个工厂类来创建对象,通常包含一个用于创建对象的静态方法,该方法根据传入的参数返回不同类的对象实例。
- 工厂方法模式:通过定义一个用于创建对象的接口,但将实际创建工作延迟到子类。每个子类都可以实现工厂接口以提供具体的对象实例化过程。
- 抽象工厂模式:抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定具体的类。
# 简单工厂
一个demo实例
// 简单工厂类
class CarFactory {
// 静态方法,根据传入的类型创建不同的汽车对象
public static Car createCar(String type) {
if ("BMW".equalsIgnoreCase(type)) {
return new BMW();
} else if ("Benz".equalsIgnoreCase(type)) {
return new Benz();
} else {
throw new IllegalArgumentException("Invalid car type: " + type);
}
}
}
//
// 客户端类
public class Client {
public static void main(String[] args) {
// 使用简单工厂创建宝马汽车对象
Car bmw = CarFactory.createCar("BMW");
bmw.start();
// 使用简单工厂创建奔驰汽车对象
Car benz = CarFactory.createCar("Benz");
benz.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
上述例子中,对象的创建就是通过CarFactory.createCar()
传入不同参数来实现不同对象的创建
总结:简单工厂模式将对象的创建和使用分离,它也是最为常用的,在开发过程中我们常看到xxxFactory.xxx就是该模式的应用。它适合————需要创建的对象数量较少且不会经常变动时。
但它违背了开闭原则,后续我们需要扩展对象种类时,只能在工厂类中进行修改。
# 工厂方法模式
它解决了静态工厂的问题,它将对象的创建责任转移到了子类,每个子类都有自己的工厂方法,这样当增加新产品时,只需要增加新的子类即可,无需修改原有代码,遵循了开放封闭原则。
demo
// 工厂接口
interface CarFactory {
Car createCar();
}
// 宝马工厂类
class BMWFactory implements CarFactory {
@Override
public Car createCar() {
return new BMW();
}
}
// 奔驰工厂类
class BenzFactory implements CarFactory {
@Override
public Car createCar() {
return new Benz();
}
}
// 测试类
public class Client {
public static void main(String[] args) {
// 使用宝马工厂创建宝马汽车对象
CarFactory bmwFactory = new BMWFactory();
Car bmw = bmwFactory.createCar();
bmw.start();
// 使用奔驰工厂创建奔驰汽车对象
CarFactory benzFactory = new BenzFactory();
Car benz = benzFactory.createCar();
benz.start();
}
}
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
在上述例子中,工厂类只是一个抽象类或者接口,具体实现由子类完成。
这样一来当需要添加新的产品时,只需要添加新的产品类和对应的工厂类,而无需修改原有的客户端代码,从而遵循了开闭原则。
# 抽象工厂模式
它的定义是:提供一个创建一系列相关或相互依赖对象的接口,,而无需指定具体的类。
一个简单的例子,我们的产品是苹果浏览器跟谷歌浏览器、苹果系统和微软系统。定义两个产品接口以及它们的实现类
// 产品接口1:操作系统
interface OperatingSystem {
void start();
void shutdown();
}
// 产品接口2:浏览器
interface Browser {
void open();
void close();
}
// 操作系统实现类:Windows
class Windows implements OperatingSystem {
@Override
public void start() {
System.out.println("Windows启动");
}
@Override
public void shutdown() {
System.out.println("Windows关闭");
}
}
// 操作系统实现类:macOS
class macOS implements OperatingSystem {
@Override
public void start() {
System.out.println("macOS启动");
}
@Override
public void shutdown() {
System.out.println("macOS关闭");
}
}
// 浏览器实现类:Chrome
class Chrome implements Browser {
@Override
public void open() {
System.out.println("Chrome浏览器打开");
}
@Override
public void close() {
System.out.println("Chrome浏览器关闭");
}
}
// 浏览器实现类:Safari
class Safari implements Browser {
@Override
public void open() {
System.out.println("Safari浏览器打开");
}
@Override
public void close() {
System.out.println("Safari浏览器关闭");
}
}
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
接着我们实现对应工厂:
// 抽象工厂接口
interface SoftwareFactory {
OperatingSystem getOperatingSystem();
Browser getBrowser();
}
// Windows软件工厂
class WindowsFactory implements SoftwareFactory {
@Override
public OperatingSystem getOperatingSystem() {
return new Windows();
}
@Override
public Browser getBrowser() {
return new Chrome();
}
}
// macOS软件工厂
class macOSFactory implements SoftwareFactory {
@Override
public OperatingSystem getOperatingSystem() {
return new macOS();
}
@Override
public Browser getBrowser() {
return new Safari();
}
}
// 测试类
// 客户端类
public class Client {
public static void main(String[] args) {
// 使用Windows软件工厂创建产品
SoftwareFactory windowsFactory = new WindowsFactory();
OperatingSystem windowsOS = windowsFactory.getOperatingSystem();
Browser chromeBrowser = windowsFactory.getBrowser();
windowsOS.start();
chromeBrowser.open();
chromeBrowser.close();
windowsOS.shutdown();
// 使用macOS软件工厂创建产品
SoftwareFactory macOSFactory = new macOSFactory();
OperatingSystem macOS = macOSFactory.getOperatingSystem();
Browser safariBrowser = macOSFactory.getBrowser();
macOS.start();
safariBrowser.open();
safariBrowser.close();
macOS.shutdown();
}
}
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
在这个例子中,SoftwareFactory
是一个抽象工厂接口,它定义了两个方法 getOperatingSystem
和 getBrowser
来创建操作系统和浏览器对象。WindowsFactory
和 macOSFactory
分别实现了这个接口,并提供了创建 Windows 和 macOS 操作系统以及对应浏览器的具体实现。客户端代码通过创建不同的工厂对象来获取相应类型的操作系统和浏览器对象,并调用它们的方法。
# 工厂跟抽象工厂
工厂方法跟抽象工厂似乎比较相似,但细心的朋友应该会发现,它们的区别在于创建产品是否单一上。
- 当需要创建的对象是单一产品时,适合使用工厂方法模式。
- 当需要创建的对象是一系列相互关联或相互依赖的产品族时,适合使用抽象工厂模式。
# 模版方法模式
# 定义
模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,并将一些步骤延迟到子类中实现。这样,子类可以在不改变算法结构的情况下重定义算法的某些特定步骤。
简单的说,就是用抽象类把公共的方法集合在一起,不同的就让子类去实现。
所需的角色为两个:抽象模版以及 具体模版
public class TemplateTest {
public static void main(String[] args) {
//炒包菜
BaoCai baoCai = new BaoCai();
baoCai.cookProcess();
System.out.println("-------------");
//炒白菜
BaiCai baiCai = new BaiCai();
baiCai.cookProcess();
}
}
abstract class AbstractClass{ //抽象类
//模板方法定义
public final void cookProcess() {
this.pourOil();//倒油
this.heatOil();//热油
this.pourVegetable();//倒蔬菜
this.pourSauce();//倒调味料
this.fry();//翻炒
}
public abstract void pourVegetable();//倒蔬菜是不一样的(一个下包菜,一个是下白菜)
public abstract void pourSauce();//倒调味料是不一样
public void pourOil() {System.out.println("倒油");}
public void heatOil() {System.out.println("热油");}
public void fry(){System.out.println("炒啊炒");}
}
class BaoCai extends AbstractClass{
public void pourVegetable() {System.out.println("加入包菜");}
public void pourSauce() {System.out.println("加入辣椒酱");}
}
class BaiCai extends AbstractClass{
public void pourVegetable() {System.out.println("加入白菜");}
public void pourSauce() {System.out.println("加入盐和味精");}
}
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
# 总结
模板方法模式其实就是一种基于继承的代码复用技术。在日常开发中我们也应该尽量复用代码,尽量避免冗余。