【学习笔记】设计模式

PS:该笔记是学习尚硅谷—韩顺平—图解设计模式,记录的笔记。


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
// 饿汉式、静态常量
class Singleton{
//1. 私有化构造器,外部不能new
private Singleton(){ }
//2. 内部创建对象实例
private final static Singleton instance = new Singleton();
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
// 饿汉式、静态代码块
class Singleton{
//1. 私有化构造器,外部不能new
private Singleton(){
}
//2. 本地内部创建对象实例
private static Singleton instance;
static {
instance = new Singleton();
}
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
  • 优点:这种写法简单,在类装载的时候就完成了实例化,避免了线程同步问题。
  • 缺点:在类装载的时候就完成实例化,没有达到 懒加载 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
  • 使用场景:简单、可用、没有完成懒加载、在一定会使用该类的情况下,可采用该方法。

二、懒汉式(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//懒汉式,线程不安全
class Singleton {
// 1. 创建一个对象实例
private static Singleton instance;
// 2. 构造器私有化
private Singleton() {}
// 3. 提供一个公有的静态方法,返回实例
public static Singleton getInstance() {
// 如果没有创建该实例,则创建该实例,否则,直接返回
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
  • 优点:起到了 懒加载 的效果,但是只能应用在单线程下。
  • 缺点:如果在多线程下,一个线程进入了 if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

三、懒汉式(线程安全)

在前一个基础上,使用synchronized对方法进行同步,解决线程安全问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//线程安全、同步方法
class Singleton {
// 1. 创建一个对象实例
private static Singleton instance;
// 2. 构造器私有化
private Singleton() {}
// 3. 使用 synchronized 关键字,提供一个公有的 同步的 静态方法,返回实例
public static synchronized Singleton getInstance() {
// 如果没有创建该实例,则创建该实例,否则,直接返回
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
  • 优点:解决了线程安全问题。
  • 缺点:效率太低了,每个线程在想获得类的实例时候,执行 getInstance方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。方法进行同步效率太低。
  • 在实际开发中,不推荐使用这种方式。

四、双重检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 双重检查
class Singleton {
// 1. 建立一个同步对象实例(将数据同步到内存)。
private static volatile Singleton instance;
// 2. 私有化构造方法
private Singleton() { }
//3. 提供一个静态的公有方法,加入双重检查代码,解决线程安全和懒加载问题。
public static synchronized Singleton getInstance() {
if (instance == null){
synchronized (Singleton.class){
if (instance==null){
instance = new Singleton();
}
}
}
return instance;
}
}
  • 代码中使用两次if检查,保证了线程安全。
  • 实例化代码只用执行一次,后面再次访问时,判断if,直接 return 实例化对象,避免了反复进行方法同步。
  • 优点:线程安全;延迟加载;效率较高。

五、静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
// 1.构造器私有化
private Singleton() { }
// 2.写一个静态内部类,该类中有一个静态属性 Singleton
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
// 3.提供一个静态的公有方法,直接返回 SingletonInstance.INSTANCE
public static synchronized Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
  • 采用了类装载的机制来保证初始化实例时只有一个线程。
  • 静态内部类方式在 Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  • 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

六、枚举

1
2
3
4
//直接建立一个枚举对象
enum Singleton {
INSTANCE;
}
  • 说明:该方法采用枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

第二章:工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

一、简单工厂模式

  • 概念:它属于创建型模式,是工厂模式的一种。
    • 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。
    • 简单工厂模式是工厂模式家族中最简单实用的模式
    • 定义了一个创建对象的类,由这个类来封装实例化对象的行为(代码)。
    • 在软件开发中,当我们会用到大量的创建某种、某类或者某批对象时,就会使用到工厂模式。
  • 案例:使用简单工厂模式设计一个Pizza店。
    1. 建立Pizza的抽象类,让各种种类的Pizza继承该抽象类。
    2. 编写SimpleFactory类,通过接收的参数,选择创建不同种类的Pizza。
    3. 编写Order类,调用Factory方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PizzaSimpleFactory {
// 简单工厂模式
public static Pizza createPizza(String orderType) {
Pizza pizza = null;
System.out.println("使用简单工厂模式");
switch (orderType) {
case "greek":
pizza = new GreekPizza();
break;
case "奶酪披萨":
pizza = new CheesePizza();
break;
case "pepper":
pizza = new PepperPizza();
break;
}
return pizza;
}
}

二、工厂方法模式

  • 概念:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类。
  • 案例:针对不同种类的Pizza增加不同口味,将Pizza的实例化功能抽象成抽象方法,在不同的口味的点餐子类中实现。
    1. 编写一个OrderPizza的抽象类,其中包括一个创建Pizza类的抽象方法,与其他的通用实现方法。
    2. 让BjOrderPizza等不同类型的披萨预定类继承OrderPizza,并实现创建不同种类Pizza的方法。
    3. 让商店直接选择不同的OrderPizza类。

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
public abstract class OrderPizza {
// 1. 定义一个抽象方法,让各个工厂子类实现该方法
abstract Pizza createPizza(String orderType);
// 2. 实现其他的通用方法
public OrderPizza() {
Pizza pizza = null;
String orderType; //订购类型
do {
orderType = getType();
pizza = createPizza(orderType); // 抽象方法,由各个子类完成
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}while (true);
}
}
// 不同口味的类继承
public class BjOrderPizza extends OrderPizza{
@Override
Pizza createPizza(String orderType) {
Pizza pizza = null;
switch (orderType) {
case "cheese":
pizza = new BjCheesePizza();
break;
case "pepper":
pizza = new BjPepperPizza();
break;
}
return pizza;
}
}

三、抽象工厂方法

  • 概念:定义了一个 interface 用于创建相关或有依赖关系的对象簇,而无需指明具体的类。
    • 抽象工厂模式可以将简单工厂模式和工厂方法模式进行整合。
    • 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)。
    • 将工厂抽象成两层,抽象工厂 和 具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。
  • 案例:
    1. 编写AbstractFactory 接口,以及创建Pizza的方法。
    2. 编写不同口味的Pizza工厂类,继承该接口。
    3. 编写OrderPizza类,使用AbstractFactory 传入数据。

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
//1.编写AbstractFactory 接口,以及创建Pizza的方法。
public interface AbstractFactory {
public Pizza createPizza(String orderType);
}
//2.编写不同口味的Pizza工厂类,继承该接口。
public class BjFactory implements AbstractFactory {
@Override
public Pizza createPizza(String orderType) {
System.out.println("抽象工厂方法");
Pizza pizza = null;
switch (orderType) {
case "cheese":
pizza = new BjCheesePizza();
break;
case "pepper":
pizza = new BjPepperPizza();
break;
}
return pizza;
}
}
//3.编写OrderPizza类,使用AbstractFactory 传入数据。
public class OrderPizza {
AbstractFactory abstractFactory;
public OrderPizza(AbstractFactory abstractFactory) {
setAbstractFactory(abstractFactory);
}
private void setAbstractFactory(AbstractFactory factory) {
Pizza pizza = null;
String orderType = "";
this.abstractFactory = factory;
do {
orderType = getType();
pizza = factory.createPizza(orderType);
if (pizza != null) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
} else {
System.out.println("fail");
}
} while (true);
}
}
// 主方法
String taste = getType();
if ("bj".equals(taste)){
BjFactory bjFactory = new BjFactory();
new OrderPizza(bjFactory);
}else if ("orign".equals(taste)){
OriginFactory originFactory = new OriginFactory();
new OrderPizza(originFactory);
}

第三章:原型模式

原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。

通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()。

可以理解成孙大圣拔出猴毛, 变出其它孙大圣。

一、设计思想

  • Prototype : 原型类,声明一个克隆自己的接口
  • ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作
  • Client: 让一个原型对象克隆自己,从而创建一个新的对象(属性一样)

二、原型模式

  • 基本数据类型:可以直接继承clone方法。
  • 引用数据类型:需使用特殊方法处理,否则只是拷贝地址。
  • 写法1:类实现Cloneable接口,基本数据类型可直接使用父类的clone()方法,而引用数据类型则进行单独处理。
  • 写法2:类实现Serializable接口,然后对整个数据进行序列化,后反序列化得到一个新的类。
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
public class Sheep implements Cloneable,Serializable {
private String name;
private int age;
private String color;
public Sheep friend; /引用数据类型, 默认是浅拷贝
public Sheep(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
// 方法一、对引用类型的属性,进行单独处理
@Override
protected Sheep clone() throws CloneNotSupportedException {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
//对引用类型的属性,进行单独处理
sheep.friend = (Sheep) sheep.friend.clone();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return sheep;
}

// 方法二、使用序列化实现
protected Sheep deepClone() throws IOException {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (Sheep) ois.readObject();
} catch (Exception e) {
return null;
} finally {
bos.close();
oos.close();
bis.close();
ois.close();
}
}
}

第四章:建造者模式

建造者模式是一种对象构造模式,它将复杂对象的建造过程抽象出来,使这个抽象过程的不同实现方式可以构造出不同属性的对象。

一、设计思想

  • 产品角色 Product:一个具体的产品对象。
  • 抽象建造者 Builder:创建一个 Product 对象的各个部件指定的 接口/抽象类。
  • 具体建造者 Concrete Builder :实现接口,构建和装配各个部件。
  • 指挥者 Director:构建一个使用 Builder 接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

二、建造者模式

需要建房子:这一过程为打桩、砌墙、封顶。不管是普通房子也好,别墅也好都需要经历这些过程,下面我们使用建造者模式(Builder Pattern)来完成。

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
// 产品
public class House {
private String base;
private String wall;
private String roofed;
}
// 抽象建造者,包工头
public abstract class HouseBuilder {
protected House house = new House();
public abstract void buildBasic();//打地基
public abstract void buildWall();//砌墙
public abstract void roofed();//盖屋顶
//建好房子
public House buildHouse(){
return house;
}
}
// 具体建造者
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("普通房子打地基");
}
@Override
public void buildWall() {
System.out.println("普通房子砌墙");
}
@Override
public void roofed() {
System.out.println("普通房子盖屋顶");
}
}
// 指挥者
public class HouseDirector {
HouseBuilder houseBuilder = null;
// 构造者传输houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
// 指挥方法
public House constructHouse(){
houseBuilder.buildBasic();
houseBuilder.buildWall();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
HouseDirector houseDirector;
// 盖普通房子
CommonHouse commonHouse = new CommonHouse();
houseDirector = new HouseDirector(commonHouse);
House house = houseDirector.constructHouse();
// 盖高楼房子
HighHouse highHouse = new HighHouse();
houseDirector = new HouseDirector(highHouse);
House house1 = houseDirector.constructHouse();
}
}

B.结构模式

第五章:适配器模式

适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。分类三类:类适配器模式、对象适配器模式、接口适配器模式。

一、类适配器

Java 是单继承机制,所以类适配器需要继承资源类这一点算是一个缺点,因为这要求 dst 必须是接口,有一定局限性;资源类的方法在 Adapter 中都会暴露出来,也增加了使用的成本。由于其继承了 资源类,所以它可以根据需求重写资源类的方法,使得 Adapter 的灵活性增强了。

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
// 0. 手机使用类
public class Phone {
static final int VOLTAGE_PHONE = 5;
public void charging(IVoltage5V voltage5V) {
if (voltage5V.output5V() == VOLTAGE_PHONE) {
System.out.println("开始充电");
} else {
System.out.println("不能充电");
}
}
}
// 1. 建立220V电压
public class Voltage220V {
public int output220V() {
int src = 220;
System.out.println("电压" + src + "伏");
return src;
}
}
// 2. 建立适配器接口
public interface IVoltage5V {
int output5V();//返回5v电压
}
// 3. 建立适配器类, 继承Voltage220V,实现IVoltage5V接口
public class VoltageAdapter extends Voltage220V implements IVoltage5V {
@Override
public int output5V() {
int src = output220V();
return src / 44;
}
}
// 4. 主程序
Phone phone = new Phone(); // 手机类
phone.charging(new VoltageAdapter()); // 手机充电

二、对象适配器

基本思路和类的适配器模式相同,使用关联关系(聚合)来替代继承关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//使用聚合替代继承
public class VoltageAdapter implements IVoltage5V {
private Voltage220V voltage220V; // 使用聚合替代继承
public VoltageAdapter(Voltage220V voltage220V) {
this.voltage220V = voltage220V;
}
@Override
public int output5V() {
int dstV = 0 ;
if(null != voltage220V){
int src = voltage220V.output220V(); // 调用 该输出接口
System.out.println("使用对象适配器");
dstV = src / 44;
}
return dstV;
}
}

三、接口适配器

当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求。

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
// 1. 定义适配器接口
public interface InterfaceTest {
void m1();
void m2();
void m3();
void m4();
}
// 2. 在AbsAdapter中空实现(默认实现)
public class AbsAdapter implements InterfaceTest {
@Override
public void m1() {}
@Override
public void m2() {}
@Override
public void m3() {}
@Override
public void m4() {}
}
// 3. 客户端重写需要的适配器实现
public class Client {
public static void main(String[] args) {
AbsAdapter absAdapter = new AbsAdapter(){
@Override
public void m1() {
System.out.println("使用了m1方法");
}
};
absAdapter.m1();
}
}

第六章:桥接模式

将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。对于两个独立变化的维度,使用桥接模式再适合不过了。

一、设计思想

  • Client 类:桥接模式的调用者。
  • 抽象类:维护了 实现类。
  • 抽象类的实现类 : 是 Abstraction 抽象类的子类。
  • Implementor : 行为实现类的接口。
  • 行为的具体实现类。

二、桥接模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. 定义品牌的行为接口 和 手机的抽象类
public interface Brand {
void open();
void close();
void call();
}
abstract class AbstractPhone {
private Brand brand;// 聚合
public AbstractPhone(Brand brand) {
this.brand = brand;
}
protected void open() {
this.brand.open();
}
protected void close() {
brand.close();
}
protected void call() {
brand.call();
}
}
// 2. 实现具体的接口和实现类
// 3. 使用客户端调用

第七章:装饰器模式

动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更有弹性,装饰者模式也体现了开闭原则(ocp)。

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
// 1. 创建一个主事务的接口
public interface Shape {
void draw();
}
// 2. 实现该接口
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Rectangle");
}
}
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
// 3. 创建装饰类,实现事务的接口。
public abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
// 4. 扩展该接口
public class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
// 5. 使用装饰器
public class DecoratorPatternDemo {
public static void main(String[] args) {
Shape circle = new Circle();
ShapeDecorator redCircle = new RedShapeDecorator(new Circle());
}
}

第八章、策略模式

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。解决复杂业务中过多的if else导致的可读性和可维护性的问题。

参考掘金中《业务复杂=if else?刚来的大神竟然用策略+工厂彻底干掉了他们!》,写的非常好。

需求是这样的:

  1. 外卖平台上的某家店铺为了促销,设置了多种会员优惠,其中包含超级会员折扣8折、普通会员折扣9折和普通用户没有折扣三种。
  2. 希望用户在付款的时候,根据用户的会员等级,就可以知道用户符合哪种折扣策略,进而进行打折,计算出应付金额。
  3. 随着业务发展,新的需求要求专属会员要在店铺下单金额大于30元的时候才可以享受优惠。
  4. 接着,又有一个变态的需求,如果用户的超级会员已经到期了,并且到期时间在一周内,那么就对用户的单笔订单按照超级会员进行折扣,并在收银台进行强提醒,引导用户再次开通会员,而且折扣只进行一次。

如果使用if-else:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
if (用户是专属会员) {
if (订单金额大于30元) {
returen 7折价格;
}
}
if (用户是超级会员) {
return 8折价格;
}
if (用户是普通会员) {
if(该用户超级会员刚过期并且尚未使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
return 原价;
}

使用策略模式:

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
// 1. 定义一个策略接口
public interface UserPayService {
public BigDecimal quote(BigDecimal orderPrice);//计算应付价格
}
// 2. 定义相应的策略类
// 专属会员
public class ParticularlyVipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if (消费金额大于30元) {
return 7折价格;
}
}
}
// 超级会员
public class SuperVipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
return 8折价格;
}
}
// 普通用户
public class VipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if(该用户超级会员刚过期并且尚未使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
}
// 3. 执行策略
public BigDecimal calPrice(BigDecimal orderPrice,User user) {
String vipType = user.getVipType();
if (vipType == 专属会员) {
//伪代码:从Spring中获取超级会员的策略对象
UserPayService strategy = Spring.getBean(ParticularlyVipPayService.class);
return strategy.quote(orderPrice);
}
if (vipType == 超级会员) {
UserPayService strategy = Spring.getBean(SuperVipPayService.class);
return strategy.quote(orderPrice);
}
if (vipType == 普通会员) {
UserPayService strategy = Spring.getBean(VipPayService.class);
return strategy.quote(orderPrice);
}
return 原价;
}

虽然在计算价格的时候没有if-else了,但是选择具体的策略的时候还是不可避免的还是要有一些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
// 1. 为了方便我们从Spring中获取UserPayService的各个策略类,我们创建一个工厂类
public class UserPayServiceStrategyFactory {
private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>();//用来保存所有的策略类的实例
public static UserPayService getByUserType(String type){
return services.get(type); //根据类型直接获取对应的类的实例
}
public static void register(String userType,UserPayService userPayService){
Assert.notNull(userType,"userType can't be null");
services.put(userType,userPayService);
}
}
// 2. Spring Bean的注册
// 使用InitializingBean接口,凡是继承该接口的类,在bean的属性初始化后都会执行afterPropertiesSet方法,将bean注册到策略的实例中去。
@Service
public class ParticularlyVipPayService implements UserPayService,InitializingBean {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if (消费金额大于30元) {
return 7折价格;
}
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("ParticularlyVip",this);
}
}

@Service
public class SuperVipPayService implements UserPayService ,InitializingBean{
@Override
public BigDecimal quote(BigDecimal orderPrice) {
return 8折价格;
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("SuperVip",this);
}
}

@Service
public class VipPayService implements UserPayService,InitializingBean {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if(该用户超级会员刚过期并且尚未使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("Vip",this);
}
}
// 3. 计算价格
public BigDecimal calPrice(BigDecimal orderPrice,User user) {
String vipType = user.getVipType(); // 获取类型
UserPayService strategy = UserPayServiceStrategyFactory.getByUserType(vipType); // 根据类型获取策略
return strategy.quote(orderPrice); // 计算价格
}

但是,上面还遗留了一个问题,那就是UserPayServiceStrategyFactory中用来保存所有的策略类的实例的Map是如何被初始化的?各个策略的实例对象如何塞进去的呢?

Spring Bean的注册

UserPayServiceStrategyFactory中提供了的register方法是用来注册策略服务的。我们调用register方法,把Spring通过IOC创建出来的Bean注册进去就行了。这种需求,可以借用Spring种提供的InitializingBean接口,这个接口为Bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。那么,我们将前面的各个策略类稍作改造即可。

只需要每一个策略服务的实现类都实现InitializingBean接口,并实现其afterPropertiesSet方法,在这个方法中调用UserPayServiceStrategyFactory.register即可。这样,在Spring初始化的时候,当创建VipPayServiceSuperVipPayServiceParticularlyVipPayService的时候,会在Bean的属性初始化之后,把这个Bean注册到UserPayServiceStrategyFactory中。

第九章:代理模式

Java三种代理模式:静态代理、动态代理和cglib代理

一、为什么使用代理模式

我们在写一个功能函数时,经常需要在其中写入与功能不是直接相关但很有必要的代码,如日志记录,信息发送,安全和事务支持等,这些枝节性代码虽然是必要的,但它会带来一些麻烦。

二、定义

为其他对象提供一个代理以控制对某个对象的访问,即通过代理对象访问目标对象。

这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。

三、静态代理

这种代理方式需要代理对象和目标对象实现一样的接口。

  • 优点:可以在不修改目标对象的前提下扩展目标对象的功能。
  • 缺点:
    • 冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
    • 不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
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
// 1. 对象接口
public interface ITeacherDao {
void teach();
}
// 2. 目标对象
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师教学");
}
}
// 3. 代理对象
public class TeacherDaoProxy implements ITeacherDao {
private ITeacherDao teacherDao;
public TeacherDaoProxy(ITeacherDao teacherDao) {
this.teacherDao = teacherDao;
}
@Override
public void teach() {
System.out.println("代理开始");
teacherDao.teach();
System.out.println("代理结束");
}
}
// 4. 客户端
public class Client {
public static void main(String[] args) {
TeacherDao teacherDao = new TeacherDao();
TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(teacherDao);
teacherDaoProxy.teach();
}
}

四、动态代理

动态代理利用了JDK API,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。

静态代理与动态代理的区别主要在:

  • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
  • 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中。

特点:动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

JDK中生成代理对象主要涉及的类有

1
2
3
4
5
6
7
8
// 代理对象
static Object newProxyInstance(
ClassLoader loader, //指定当前目标对象使用类加载器
Class<?>[] interfaces, //目标对象实现的接口的类型
InvocationHandler h //事件处理器
)
// 实现接口 InvocationHandler 的方法
Object invoke(Object proxy, Method method, Object[] args)// 在代理实例上处理方法调用并返回结果。
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
// 1. 接口
public interface ITeacherDao {
void teach();
void sayName(String name);
}
// 2. 实现类
public class TeacherDao implements ITeacherDao {
@Override
public void teach() {
System.out.println("老师教学");
}
@Override
public void sayName(String name) {
System.out.println("说" + name + "的名字");
}
}
// 3. 动态代理工厂
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(proxy, method, args) -> { // 执行代理方法
System.out.println("JDK代理开始");
Object object = method.invoke(target,args);
System.out.println("JDK代理结束");
return object;
});
}
}
// 4. 客户端
public class Client {
public static void main(String[] args) {
ITeacherDao teacherDao = new TeacherDao();
ITeacherDao proxy = (ITeacherDao) new ProxyFactory(teacherDao).getProxyInstance();
proxy.sayName("tom");
proxy.teach();
}
}

五、cglib代理

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

cglib特点

  • jdk的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用cglib实现。
  • cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
  • cglib包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。

cglib与动态代理最大的区别就是

  • 使用动态代理的对象必须实现一个或多个接口
  • 使用cglib代理的对象则无需实现接口,达到代理类无侵入。

使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。

cglib的Maven坐标

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
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
// 1. 目标对象
public class TeacherDao{
public void teach() {
System.out.println("老师教学");
}
}
// 2. cglib代理
public class ProxyFactory implements MethodInterceptor{
private Object target;//维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}
//为目标对象生成代理对象
public Object getProxyInstance() {
Enhancer en = new Enhancer();//工具类
en.setSuperclass(target.getClass());//设置父类
en.setCallback(this);//设置回调函数
return en.create();//创建子类对象代理
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事务");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("关闭事务");
return returnValue;
}
}
// 3. 客户端
public class Client {
public static void main(String[] args) {
TeacherDao teacherDao = new TeacherDao();
TeacherDao proxy = (TeacherDao) new ProxyFactory(teacherDao).getProxyInstance();
proxy.teach();
}
}

六、与装饰模式的区别

  • 装饰器模式:能动态的新增或组合对象的行为。

    在不改变接口的前提下,动态扩展对象的功能

  • 代理模式:为其他对象提供一种代理以控制对这个对象的访问。

    在不改变接口的前提下,控制对象的访问