设计模式

设计模式分类

设计模式分为三大类

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

工厂模式

简单工厂模式(不属于设计模式)

适⽤于⼯⼚类负责创建对象较少的情况,缺点是如果要增加新产品,就需要修改⼯⼚类的判断逻辑,违背开闭原则,且产品多的话会使⼯⼚类⽐较复杂。

举例:

工厂类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 创建一个抽象类:抽象子类的共有方法
* 创建子类实现父类的抽象方法
* 在工厂方法中根据类型创建不同的具体对象
**/
public class SimpleCoffeeFactory {
// 根据type判断类型,实例化并返回对应对象
public Coffee createCoffee(String type) {
Coffee coffee = null;
if("americano".equals(type)) {
coffee = new AmericanoCoffee();
} else if("latte".equals(type)) {
coffee = new LatteCoffee();
}
return coffee;
}
}

工厂方法模式

定义了一个创建对象的抽象方法,由子类决定要实例化的类

举例:

(我们依然举pizza工厂的例子,不过这个例子中,pizza产地有两个:伦敦和纽约)。添加了一个新的产地,如果用简单工厂模式的的话,我们要去修改工厂代码,并且会增加一堆的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
/**
* 抽象工厂
**/
public interface CoffeeFactory {
Coffee createCoffee();
}

/**
* 具体工厂
*
* 抽象产品为coffee,具体产品为LatteCoffee和AmericanCoffee
* 这种工厂模式可以通过不同的具体工厂创建出不同的具体产品
**/
public class LatteCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
}

public class AmericanCoffeeFactory implements CoffeeFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
}

解决了简单工厂模式的问题,如果新增一个产地,则只需要增加一个类就可以了。

工厂方法存在的问题与解决方法:客户端需要创建类的具体的实例。简单来说就是用户要订纽约工厂的披萨,他必须去纽约工厂,想订伦敦工厂的披萨,必须去伦敦工厂。 当伦敦工厂和纽约工厂发生变化了,用户也要跟着变化,这无疑就增加了用户的操作复杂性。为了解决这一问题,我们可以把工厂类抽象为接口,用户只需要去找默认的工厂提出自己的需求(传入参数),便能得到自己想要产品,而不用根据产品去寻找不同的工厂,方便用户操作。这也就是我们接下来要说的抽象工厂模式。

抽象工厂模式

定义了一个接口用于创建相关或有依赖关系的对象族,而无需明确指定具体类。

举例:

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
/**
* 抽象工厂
**/
public interface DessertFactory {
Coffee createCoffee();
Dessert createDessert();
}

/**
* 具体工厂
**/
public class AmericanDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new AmericanCoffee();
}
public Dessert createDessert() {
return new MatchaMousse();
}
}

public class ItalyDessertFactory implements DessertFactory {
public Coffee createCoffee() {
return new LatteCoffee();
}
public Dessert createDessert() {
return new Tiramisu();
}
}

单例模式

⼀个单例类在任何情况下都只存在⼀个实例,构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例,对外提供⼀个静态公有⽅法获取实例。

饿汉式(线程安全)

顾名思义,类⼀加载就创建对象,这种⽅式⽐较常⽤,但容易产⽣垃圾对象,浪费内存空间

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

缺点:没有加锁,执行效率会提高。

优点:类加载时就初始化,浪费内存。

懒汉式

线程安全的写法:(双重锁)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {  
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

为什么要使用两次校验:

(1)第一次校验

因为单例模式只需要创建一次实例,如果后面再调用getInstance方法时直接返回之前创建的实例,。因此不需要执行同步方法块里面的代码,大大提高了性能,如果不加,每次都要竞争锁。

(2)第二次校验

如果线程1 执行第一次校验后,判断对象为null,这时线程2获取了CPU,也执行了第一次校验,判断对象为null,此时线程2创建对象,这时线程1又获得了CPU,由于之前已经校验过为null了,不会再次判断,所以接着也创建了一个对象,结果就会导致创建了多个实例。

为什么要加上volatile关键字

当执行singleton = new Singleton();这段代码时,其实是分三步进行执行的:

(1)为singleton 分配内存空间

(2)初始化singleton

(3)将singleton 指向分配的内存空间。

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。

为什么不使用静态内部类

静态内部类实现单例模式代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {

//声明为 private 避免调用默认构造方法创建对象
private Singleton() {
}

// 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
  • Java 加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例

适配器模式

在我们的应⽤程序中我们可能需要将两个不同接⼝的类来进⾏通信,在不修改这两个的前提下我们可能会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。所谓适配器模式就是将⼀个类的接⼝,转换成客户期望的另⼀个接⼝。它可以让原本两个不兼容的接⼝能够⽆缝完成对接。

类适配器

通过类继承来实现适配。

适配器通过继承源实现目标来实现适配器

举例:

源(Adapee)角色

1
2
3
4
5
6
public class Cat {
public void makeSound(){
System.out.println("猫猫:喵喵喵。。。。。。。。。。。。。");
}
}

目标(Target)角色

1
2
3
4
public interface OurFriend {
void speak();
}

适配器(Adapter)角色

1
2
3
4
5
6
public class CatFriend extends Cat implements OurFriend{
@Override
public void speak() {
super.makeSound();
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Person {
public void speakTo(OurFriend friend){
System.out.println("人:你在干嘛?");
friend.speak();
}

public static void main(String[] args) {
Person person=new Person();
OurFriend friend=new CatFriend();
person.speakTo(friend);
}
}

如果再来个别的动物,则再增加一个别的动物的适配器,有没有一种办法,可以时各种动物的适配器,则看对象适配器。

对象适配器

通过类对象组合来实现适配

我们希望可以有一个可以和各种动物做朋友的办法,而不是每次有了新的动物朋友都需要增加一个适配器。

举例:

让源(Adapee)角色的猫和狗实现动物接口

1
2
3
4
5
6
public class Dog implements IAnimal{
public void makeSound(){
System.out.println("狗:汪汪汪汪。。。。。。。。。");
}
}

1
2
3
4
5
6
public class Cat implements IAnimal{
public void makeSound(){
System.out.println("猫猫:喵喵喵。。。。。。。。。。。。。");
}
}

万物拟人适配器(Adaper)角色

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AnimalFriendAdaper implements OurFriend{
private IAnimal animal;

public AnimalFriendAdaper(IAnimal animal){
this.animal=animal;
}

@Override
public void speak() {
animal.makeSound();
}
}

测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Person {
public void speakTo(OurFriend friend){
System.out.println("人:你在干嘛?");
friend.speak();
}

public static void main(String[] args) {
// 一个人
Person person = new Person();
// 一只狗
IAnimal dog = new Dog();
// 一只猫
IAnimal cat = new Cat();
// 万物拟人
person.speakTo(new AnimalFriendAdaper(dog));
person.speakTo(new AnimalFriendAdaper(cat));

}
}

代理模式

代理模式的本质是⼀个中间件,主要⽬的是解耦合服务提供者和使⽤者。使⽤者通过代理间接的访问服务提供者,便于后者的封装和控制。

静态代理

举例:

以租房为例,租客找房东租房,然后中间经过房屋中介,以此为背景,

静态代理的前提,那就是真实类和代理类要实现同一个接口,在代理类中实现真实类的方法同时可以进行真实类方法的增强处理,在一个代理类中就可以完成对多个真实对象的注入工作。

租房:

1
2
3
4
public interface IRentHouse {
void rentHouse();
}

房东类

1
2
3
4
5
6
7
8
public class RentHouse implements IRentHouse {

@Override
public void rentHouse() {
System.out.println("实现租房");
}
}

代理:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class IntermediaryProxy implements IRentHouse {
private IRentHouse iRent;
public IntermediaryProxy(IRentHouse iRentHouse) {
iRent=iRentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介费");
iRent.rentHouse();
System.out.println("中介负责维修管理");
}
}

可以看出房东类(RentHouse)和代理类(IntermediaryProxy)都实现了租房接口,这就是一个静态代理的前提

从静态代理的代码中可以发现,静态代理的缺点显而易见,那就是当真实类的方法越来越多的时候,这样构建的代理类的代码量是非常大的,所以就引进动态代理.

动态代理

https://blog.csdn.net/weixin_43953283/article/details/125783249

JDk动态代理

想要实现动态代理,要解决以下两个问题:

  • 根据加载到内存中的被代理类,动态的创建一个代理类的对象
  • 通过代理类对象调用方法a时,如何动态的去调用被代理类中的同名方法a
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
93
94
95
96
97
98
99
100
101
102
/**
*
* 动态代理的举例
*
* @author shkstart
* @create 2019 上午 10:18
*/

interface Human{

String getBelief();

void eat(String food);

}
//被代理类
class SuperMan implements Human{


@Override
public String getBelief() {
return "I believe I can fly!";
}

@Override
public void eat(String food) {
System.out.println("我喜欢吃" + food);
}
}

/*
要想实现动态代理,需要解决的问题?
问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。
问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a。
*/

//同于生成代理类对象
class ProxyFactory{
//调用此方法,返回一个代理类的对象。解决问题一
public static Object getProxyInstance(Object obj){//obj:被代理类的对象

MyInvocationHandler handler = new MyInvocationHandler();

//为了在MyInvocationHandler类中实例化被代理类对象
handler.bind(obj);

//生成一个代理类的对象
//obj.getClass().getClassLoader():被代理类的类加载器
//obj.getClass().getInterfaces():被代理类实现的接口(代理类也要实现)
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);
}

}



class MyInvocationHandler implements InvocationHandler{

private Object obj;//需要使用被代理类的对象进行赋值

public void bind(Object obj){
this.obj = obj;
}

//当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()
//将被代理类要执行的方法a的功能就声明在invoke()中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

//method:就是代理类所调用的方法,
//args:代理类所调用方法的参数
//这个方法就是将代理类调用的方法,被代理类去调用同名的方法
Object returnValue = method.invoke(obj,args);//相当于:被代理类.代理类调用的方法(args)=Obj.method(args);

//上述方法的返回值就作为当前类中的invoke()的返回值。
return returnValue;

}
}

public class ProxyTest {

public static void main(String[] args) {
SuperMan superMan = new SuperMan();
//proxyInstance:代理类的对象
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
//当执行到下面的方法时,将跳到上面invoke方法,再动态的调用被代理类中同名的方法。,
String belief = proxyInstance.getBelief();
System.out.println(belief);
proxyInstance.eat("四川麻辣烫");

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

NikeClothFactory nikeClothFactory = new NikeClothFactory();

ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);

proxyClothFactory.produceCloth();

}
}

也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。

CGLIB 动态代理类使用步骤

  1. 定义一个类;
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
  3. 通过 Enhancer 类的 create()创建代理类
1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
1
2
3
4
5
6
7
8
package github.javaguide.dynamicProxy.cglibDynamicProxy;

public class AliSmsService {
public String send(String message) {
System.out.println("send message:" + message);
return message;
}
}

自定义 MethodInterceptor(方法拦截器)

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
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* 自定义MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {


/**
* @param o 代理对象(增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}

}

获取代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {

public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}

静态代理和动态代理的区别:

  1. 灵活性 :动态代理更加灵活,不需要必须实现接⼝,可以直接代理实现类,并且可以不需要针对每个⽬标类都创建⼀个代理类。另外,静态
    代理中,接⼝⼀旦新增加⽅法,⽬标对象和代理对象都要进⾏修改,这是⾮常麻烦的!

  2. JVM 层⾯ :静态代理在编译时就将接⼝、实现类、代理类这些都变成了⼀个个实际的 class ⽂件。⽽动态代理是在运⾏时动态⽣成类字节
    码,并加载到 JVM 中的。

观察者模式

对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

图片

观察者模式UML图

看不懂图的人端着小板凳到这里来,给你举个栗子:假设有三个人,小美(女,22),小王和小李。小美很漂亮,小王和小李是两个程序猿,时刻关注着小美的一举一动。有一天,小美说了一句:“谁来陪我打游戏啊?”这句话被小王和小李听到了,结果乐坏了,蹭蹭蹭,没一会儿,小王就冲到小美家门口了,在这里,小美是被观察者,小王和小李是观察者,被观察者发出一条信息,然后观察者们进行相应的处理,看代码:

1
2
3
4
public interface Person {
//小王和小李通过这个接口可以接收到小美发过来的消息
void getMessage(String s);
}

这个接口相当于小王和小李的电话号码,小美发送通知的时候就会拨打getMessage这个电话,拨打电话就是调用接口,看不懂没关系,先往下看

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
public class Wang implements Person {

private String name = "小王";

public Wang() {
}

@Override
public void getMessage(String s) {
System.out.println(name + "接到了小美打过来的电话,电话内容是:" + s);
}

}

public class Li implements Person {

private String name = "小李";

public LaoLi() {
}

@Override
public void getMessage(String s) {
System.out.println(name + "接到了小美打过来的电话,电话内容是:->" + s);
}

}

代码很简单,我们再看看小美的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class XiaoMei {
List<Person> personList = new ArrayList<Person>();
public XiaoMei(){
}

public void addPerson(Person person){
personList.add(person);
}

//遍历list,把自己的通知发送给所有暗恋自己的人
public void notifyPerson() {
for(Person person:personList){
person.getMessage("你们过来吧,谁先过来谁就能陪我一起玩儿游戏!");
}
}
}

我们写一个测试类来看一下结果对不对

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) {

XiaoMei xiao_mei = new XiaoMei();
Wang wang = new Wang();
Li li = new LaoLi();

//小王和小李在小美那里都注册了一下
xiao_mei.addPerson(wang);
xiao_mei.addPerson(li);

//小美向小王和小李发送通知
xiao_mei.notifyPerson();
}
}

装饰器模式

从代码层面而言,是对类的一个扩展或者是修饰,从传统方法而言,我们可以使用继承来对某一个类进行扩展,但是往往会导致会出现非常多的子类,如果我们要想避免这种情况,那么我们就可以使用设计模式中的——装饰器模式。

装饰器模式是在不改变现有对象结构的情况下,动态地给该对象增加一些职责,即增加其额外功能。

对已有的业务逻辑进一步的封装,使其增加额外的功能,如Java中的IO流就使用了装饰者模式,用户在使用的时候,可以任意组装,达到自己想要的效果。举个栗子,我想吃三明治,首先我需要一根大大的香肠,我喜欢吃奶油,在香肠上面加一点奶油,再放一点蔬菜,最后再用两片面包夹一下,很丰盛的一顿午饭,营养又健康。那我们应该怎么来写代码呢?

首先,我们需要写一个Food类,让其他所有食物都来继承这个类,看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Food {

private String food_name;

public Food() {
}

public Food(String food_name) {
this.food_name = food_name;
}

public String make() {
return food_name;
};
}

代码很简单,我就不解释了,然后我们写几个子类继承它:

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
//面包类
public class Bread extends Food {

private Food basic_food;

public Bread(Food basic_food) {
this.basic_food = basic_food;
}

public String make() {
return basic_food.make()+"+面包";
}
}

//奶油类
public class Cream extends Food {

private Food basic_food;

public Cream(Food basic_food) {
this.basic_food = basic_food;
}

public String make() {
return basic_food.make()+"+奶油";
}
}

//蔬菜类
public class Vegetable extends Food {

private Food basic_food;

public Vegetable(Food basic_food) {
this.basic_food = basic_food;
}

public String make() {
return basic_food.make()+"+蔬菜";
}

}

这几个类都是差不多的,构造方法传入一个Food类型的参数,然后在make方法中加入一些自己的逻辑,如果你还是看不懂为什么这么写,不急,你看看我的Test类是怎么写的,一看你就明白了:

1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
Food food = new Bread(new Vegetable(new Cream(new Food("香肠"))));
System.out.println(food.make());
}
}

看到没有,一层一层封装,我们从里往外看:最里面我new了一个香肠,在香肠的外面我包裹了一层奶油,在奶油的外面我又加了一层蔬菜,最外面我放的是面包,是不是很形象,哈哈~ 这个设计模式简直跟现实生活中一摸一样,看懂了吗?我们看看运行结果吧:

图片

运行结果

一个三明治就做好了~

责任链模式

策略模式


设计模式
http://example.com/2022/10/01/设计模式/
作者
zlw
发布于
2022年10月1日
许可协议