设计模式一般可以分为三大类

  • 创建型:工厂模式,单例模式
  • 结构型:装饰器模式,适配器模式
  • 行为型:策略模式,观察者模式
    设计原则:
  • 单一职责原则
  • 开放封闭原则
  • 里式替换原则
  • 接口隔离原则
  • 依赖倒置原则
  • 迪米特原则
  • 合成复用原则

代理模式

代理模式是在不改变原先对象的前提下,通过生成一个代理对象,扩展原先对象的一些功能。比如aop,不入侵业务代码实现方法前后逻辑。

静态代理

静态代理是写死的,也就是说需要程序员手动写一个proxy代理类,与原先类共同继承一个接口(个人理解其实从实现层面是可以不共同实现一个接口的,但是这样就会导致管理混乱,代理对象如果能复用这个接口那就会逻辑上通顺很多)

静态代理实现步骤:

  • 定义一个接口及其实现类;
  • 创建一个代理类同样实现这个接口
  • 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

一旦要新增方法,代理对象也会改变,不满足设计模式中开放封闭的原则。这些代理类实际都是在编译过程中产生的class文件

动态代理

分为jdk动态代理和gclib。前者必须要求代理类实现了某个接口,后者没有这点限制。但是相对于jkd而言,gclib效率较低速度较慢。gclib的原理是生成一个子类来代理父类,由于不能继承final类,所以gclib不能代理final类

jdk动态代理

  1. Proxy.newProxyInstance方法,得到动态代理对象。传入参数是对象的类加载器,类的接口和一个InvocationHandler自定义的处理逻辑
  2. 新建一个类实现InvocationHandler,这里面只有一个方法invoke,包装了反射中的method.invoke,我们需要在这句调用前后加上自己的逻辑就可以简单实现aop。
  3. 新建一个工厂类的静态方法来获取这个动态代理对象
  4. 使用
1
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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义一个接口
public interface HelloService {
void sayHello(String name);
}

// 接口的实现类
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("你好," + name + "!");
}
}

// InvocationHandler实现类
public class HelloInvocationHandler implements InvocationHandler {
private Object target;

public HelloInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法调用前的处理逻辑");
Object result = method.invoke(target, args);
System.out.println("方法调用后的处理逻辑");
return result;
}
}

// 使用动态代理
public class ProxyDemo {
public static void main(String[] args) {
// 被代理的对象
HelloService helloService = new HelloServiceImpl();

// 创建InvocationHandler
InvocationHandler handler = new HelloInvocationHandler(helloService);

// 创建代理对象
HelloService proxyInstance = (HelloService) Proxy.newProxyInstance(
helloService.getClass().getClassLoader(),
helloService.getClass().getInterfaces(),
handler
);

// 调用代理对象的方法
proxyInstance.sayHello("张三");
}
}

gclib动态代理

  1. 首先自定义MethodInterceptor,这一点与上面的InvocationHandler是一致的。但是注意这里不是method.invoke来调用实际逻辑了。这里用methodProxy来调用原始方法。
  2. 使用的时候首先创建增强类Enhancer
  3. 设置类加载器和父类(也就是被代理对象)
  4. 设置拦截器,就是第一点创建的对象
  5. 最后获取这个代理类
1
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
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 目标类
public class Person {
public void sayHello(String name) {
System.out.println("你好," + name + "!");
}
}

// 自定义的MethodInterceptor
public class PersonMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 在方法执行前添加逻辑
System.out.println("代理类:方法调用前的处理");

// 调用目标方法
Object result = proxy.invokeSuper(obj, args);

// 在方法执行后添加逻辑
System.out.println("代理类:方法调用后的处理");

return result;
}
}

// 客户端代码
public class CglibProxyDemo {
public static void main(String[] args) {
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class); // 设置被代理类的父类
enhancer.setCallback(new PersonMethodInterceptor()); // 设置回调

// 创建代理对象
Person proxyPerson = (Person) enhancer.create();

// 调用代理对象的方法
proxyPerson.sayHello("张三");
}
}

在spring aop中,如果某个类实现了接口,则默认使用jdk动态代理;否则使用cglib

单例模式

保证只有一个全局访问点,无论怎么访问都只有一个对象。

饿汉式单例

1
2
3
4
5
6
7
public class singleton {
private static final singleton INSTANCE = new singleton();
private singleton(){}
public static singleton getInstance(){
return INSTANCE;
}
}

在类加载初始化的时候就直接创建单例对象,适合于程序运行期间始终都需要这个对象的场景。

懒汉式单例

1
2
3
4
5
6
7
8
9
10
public class singleton {
private static singleton INSTANCE;
private singleton(){}
public static singleton getINSTANCE(){
if (INSTANCE == null){
INSTANCE = new singleton();
}
return INSTANCE;
}
}

但是可能会有并发安全问题,如果两个都没有获取到当前对象就会产生覆盖或者数据不一致,可以使用synchronized来锁住这个方法,但是会影响性能。

双重校验锁

上面那种情况下无论是否已经实例化了都需要获取锁,但其实如果其实已经实例化了就不需要上锁。还有一个关键问题是会指令重排。一个对象在创建1过程中会经历三步:

  1. 分配内存空间
  2. 初始化对象
  3. 指针指向该对象
    如果3重排就会变成132,此时还没有进行初始化。两个线程并发就会导致报空指针异常。为了解决这个问题需要volatile禁止指令重排。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton {
// 私有构造函数
private Singleton() {}

// volatile 关键字确保实例的可见性
private static volatile Singleton instance;

// 提供全局访问点
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

内部静态类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
// 私有构造函数
private Singleton() {}

// 静态内部类
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}

// 提供全局访问点
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}

内部静态类在被调用的时候才会被加载,而且类加载不会有线程安全问题,天然解决并发问题。

单例模式是可以被破坏的,只要通过反射拿到private的构造器就可以了

策略模式

一种方法可以用多种算法实现就可以使用策略模式,根据不同情况下用不同的算法。也可以消除”if-else”这样的控制语句。

  • 上下文:持有一个策略的引用,并提供一个接口来调用策略
  • 策略接口:定义了一系列算法的公共接口
  • 具体实现:策略的具体实现
1
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
85
86
87
88
89
90
91
92
/**
* 回执处理策略接口
**/
public interface ReceiptHandleStrategy {

void handleReceipt(Receipt receipt);
}
/**
* 策略实现类
**/
public class Mt1011ReceiptHandleStrategy implements ReceiptHandleStrategy {

@Override
public void handleReceipt(Receipt receipt) {
System.out.println("解析报文MT1011: " + receipt.getMessage());
}
}

public class Mt2101ReceiptHandleStrategy implements ReceiptHandleStrategy {

@Override
public void handleReceipt(Receipt receipt) {
System.out.println("解析报文MT2101: " + receipt.getMessage());
}
}

/**
* 上下文类,持有策略接口
**/
public class ReceiptStrategyContext {

private ReceiptHandleStrategy receiptHandleStrategy;

public void setReceiptHandleStrategy(ReceiptHandleStrategy receiptHandleStrategy) {
this.receiptHandleStrategy = receiptHandleStrategy;
}

//调用策略类中的方法
public void handleReceipt(Receipt receipt){
if(receipt != null){
receiptHandleStrategy.handleReceipt(receipt);
}
}
}
/**
* 策略工厂,用哈希表保存策略,根据参数选择策略
**/
public class ReceiptHandleStrategyFactory {

public ReceiptHandleStrategyFactory() {
}

//使用Map集合存储策略信息,彻底消除if...else
private static Map<String,ReceiptHandleStrategy> strategyMap;

//初始化具体策略,保存到map集合
public static void init(){
strategyMap = new HashMap<>();
strategyMap.put("MT1011",new Mt1011ReceiptHandleStrategy());
strategyMap.put("MT2101",new Mt2101ReceiptHandleStrategy());
}

//根据回执类型获取对应策略类对象
public static ReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){
return strategyMap.get(receiptType);
}
}

public class Client {

public static void main(String[] args) {

//模拟回执
List<Receipt> receiptList = ReceiptBuilder.genReceiptList();


//策略上下文
ReceiptStrategyContext context = new ReceiptStrategyContext();

//策略模式将策略的 定义、创建、使用这三部分进行了解耦
for (Receipt receipt : receiptList) {
//获取置策略
ReceiptHandleStrategyFactory.init();
ReceiptHandleStrategy strategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());
//设置策略
context.setReceiptHandleStrategy(strategy);
//执行策略
context.handleReceipt(receipt);
}
}
}

适配器模式

让一个实现类满足统一的接口,使不同的实现类可以一起工作。满足开放封闭原则。

  1. 例如在Spring AOP中不同的Advice实现的方式不一样类也不一样,可能实现的时候这个类的某个方法叫run,另一个叫start,为了是这些实现类完成统一,就需要加一层适配器,把名字都叫做advicerun,给客户端呈现的就是这个名字。
  2. SpringMVC也是,不同的控制器种类有很多,例如有注解的有自己写的,需要用适配器统一起来

装饰器模式

扩展类的功能,比起直接继承,装饰器模式可以实现单一责任原则,也便于功能的组合,避免类的层次结构复杂。

1
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
// 组件接口
public interface Notification {
void send(String message);
}

// 具体组件
public class SimpleNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Sending notification: " + message);
}
}

// 装饰器抽象类
public abstract class NotificationDecorator implements Notification {
protected Notification decoratedNotification;

public NotificationDecorator(Notification notification) {
this.decoratedNotification = notification;
}

@Override
public void send(String message) {
decoratedNotification.send(message);
}
}

// 具体装饰器 - 邮件
public class EmailNotificationDecorator extends NotificationDecorator {
public EmailNotificationDecorator(Notification notification) {
super(notification);
}

@Override
public void send(String message) {
super.send(message);
sendEmail(message);
}

private void sendEmail(String message) {
System.out.println("Sending email with message: " + message);
}
}

// 具体装饰器 - 短信
public class SMSNotificationDecorator extends NotificationDecorator {
public SMSNotificationDecorator(Notification notification) {
super(notification);
}

@Override
public void send(String message) {
super.send(message);
sendSMS(message);
}

private void sendSMS(String message) {
System.out.println("Sending SMS with message: " + message);
}
}

// 使用示例
public class Main {
public static void main(String[] args) {
Notification notification = new SimpleNotification();

// 添加邮件功能
notification = new EmailNotificationDecorator(notification);
notification.send("Hello!");

System.out.println("----");

// 添加短信功能
notification = new SMSNotificationDecorator(notification);
notification.send("Hello!");
}
}

观察者模式

观察者模式通过定义一对多的依赖关系,使得一个对象状态的改变能够自动通知并更新所有相关对象。它在软件开发中具有广泛的应用,尤其适用于事件驱动系统和需要解耦组件的场景。在Spring框架中,观察者模式通过事件发布和监听机制得到了高效的实现,增强了应用程序的灵活性和可维护性。

尽管观察者模式带来了许多优点,但在使用时也需注意避免过多的观察者导致系统复杂性增加,以及潜在的性能问题。合理地设计和管理观察者列表,确保事件通知的有效性和高效性,是成功应用观察者模式的关键。