二十六种设计模式

单一职责原则(代码中每个类只做单一的事情),开闭原则(对修改关闭,对扩展开放),依赖倒置(面向接口编程),合成复用(优先使用组合关系,少用继承关系)

设计模式的类型:

按照功能分为三类23种:

  1. 创建型(5种):工厂模式、抽象工厂模式、单例模式、原型模式、构建者模式
  2. 结构型(7种,类的呈现,让类变得大家都认识):适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式
  3. 行为型(11种):模板方法模式、策略模式、观察者模式、中介者模式、状态模式、责任链模式、命令模式、迭代器模式、访问者模式、解释器模式、备忘录模式。

工厂模式 抽象工厂模式


1
2
3
4
5
6
7
8
9
10
11
12
public class AnimalFactory {
public static Object createObject(String name) {
if ("cat".equals(name)) {
return new Cat();
} else if ("dog".equals(name)) {
return new Dog();
}...
else {
return null;
}
}
}

万能类,但是代码都是写死的,硬编码。

第一种思路:
优化方向:配置文件。

1
2
3
4
5
6
7
public static Object getBean(String name) {
// 优化方案
// 给对象起个名,在 xml 配置文件中,建立名称和对象的映射关系
Map<String, Object> map = new HashMap<>(); // Map 中的数据怎么来?
Object object = map.get(name);
return object;
}

第二种思路:通过不同的工厂实例,创建不同的产品实例。(抽象工厂模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface AnimalFactory {
<T> T createAnimal();
}

public class CatFactory implements AnimalFactory {
public Cat createAnimal() {
return new Cat();
}
}

public static void main(String[] args) {
AnimalFactory catFactory = new CatFactory();
Cat cat = catFactory.createAnimal();
cat.eat();
}

AnimalFactory catFactory = new CatFactory();
Cat cat2 = (Cat) catFactory.createAnimal();
cat2.eat();

AnimalFactory dogFactory = new DogFactory();
Dog dag2 = (Dog) dogFactory.createAnimal();
dag2.eat();

猫工厂生产猫,狗工厂生产狗。

原型模式


对一个对象作为原型,对其进行复制、克隆,产生一个和对象类似的新对象。

  • 浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,引用类型,指向的还是原对象所指向的。必须实现cloneable 接口。
  • 深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制。
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
public class Prototype implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String string;
private SerializableObject obj;

/* 浅复制 */
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}

/* 深复制 */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);

/* 读出二进制流产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public SerializableObject getObj() {
return obj;
}
public void setObj(SerializableObject obj) {
this.obj = obj;
}
}

class SerializableObject implements Serializable {
private static final long serialVersionUID = 1L;
}

构建者模式


复杂的对象的构造,与它的表示分离,使用同样的构建过程可以创造不同的表示。构建者模式中存在以下4个角色:

  1. builder: 为创建一个产品对象各个部件指定抽象接口
  2. ConcreteBuilder: 实现Builder的接口构造和装配该产品的各个部件,定义并明确它所创建的表示,提供一个检索产品的接口。
  3. Director: 构造一个使用 Builder 接口的对象
  4. Product: 表示被构造的复杂对象。
  • 导演类:按照一定的顺序/需求组装一个产品。
  • 构造者类:提供对产品的个性化定制,最终创建出产品
  • 产品类:最终的产品
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
// 构建器
public class StudentBuilder {
// 需要构建的对象
private Student student = new Student();
public StudentBuilder id(int id) {
student.setId(id);
return this;
}
public StudentBuilder name(String name) {
student.setName(name);
return this;
}
public StudentBuilder age(int age) {
student.setAge(age);
return this;
}
public StudentBuilder father(String fatherName) {
Father father = new Father();
father.setName(fatherName);
student.setFather(father);
return this;
}
// 构建对象
public Student build() {
return student;
}
}

// 导演类/测试类
public class BuildDemo {
public static void main(String[] args) {
StudentBuilder builder = new StudentBuilder();
// 决定如何创建一个Student
Student student = builder.age(1).name("zhangsan").father("zhaosi").build();
//builder.build(xxxxxxxx).build();
System.out.println(student);
}
}

// 产品类
public class Student {
private int id;
private String name;
private int age;
// 子产品
private Father father;
}

单例设计模式


饿汉式单例(推荐)

1
2
3
4
5
6
7
8
9
10
public class Student1 {
// 1:构造私有
private Student1() { }
// 2:成员变量初始化本身对象
private static Student1 student = new Student1();
// 3:对外提供公共方法获取对象
public static Student1 getSingletonInstance() {
return student;
}
}

懒汉式单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student5 {
private Student5() { }
/**
* 此处使用一个内部类来维护单例 JVM在类加载的时候会保护静态内部类不会被访问,是互斥的,
* 所以可以由此保证线程安全问题
*/
private static class SingletonFactory {
private static Student5 student = new Student5();
}
/* 获取实例 */
public static Student5 getSingletonInstance() {
return SingletonFactory.student;
}
}

线程安全问题,判断依据:

  1. 是否存在多线程 是
  2. 是否有共享数据 是
  3. 是否存在非原子性操作

原子性理解:只有数字赋值才是原子性的。比如:x=10;(原子性) y=x;(非原子性)
Java指令重排序:不影响结果的情况下,JVM会将程序进行优化执行(有可能执行顺序不是代码顺序),在单线程没有问题,在多线程会有问题。比如以下代码:

双重检查锁创建单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class student {
private volatile static Student student;
private Student() {}
public static Student getSingletonInstance() {
if (student == null) {
synchronized(Student.class) {
if (student == null) {
student = new Student();
// new 只是在 JVM 中开辟了堆内存,将堆内存的地址保存到栈内存中让 student 引用
// 存在的问题,第一个判断 student == null 进入创建 student 对象,
// 由于 JVM 的优化,Java指令重排序,因此只创建了内存就赋值了。并没有真正的初始化
// 这时候,第二个线程进来了 student 不为空,但实际上没有初始化调用成员变量会报空指针错误。
}
}
}
return student;
}
}

禁止Java指令重排序:volatile 修饰词。可见性保证(多线程的可见性是和主存(物理内存有关的))、禁止重排序

1
2
3
4
i = 10; // 线程1 
i++; // 线程1操作 --- 11 // 还未将高速缓存中的值写入主存。

y = i; // 线程2操作 -- 10

由于 CPU 虽然将 i++ 了高速缓存中 i 的值是11,但是还未写入主存,此时线程2访问 i 还是10. 高速缓存中的i值为不可见,如果i 用 volatile 修饰,表示,只要值改变立马写入主存中。保证了可见性。

双重检查锁 创建单例。

装饰设计模式


职责:动态的为一个对象增加新的功能

实现细节:

  1. 真实构件和装饰构件实现相同的接口,这样,客户端可以以与真实对象相同的方式同装饰对象交互。
  2. 真实对象:io流中的new FileInputSream()、new FileOutputStream()
  3. 装饰对象持有一个真实对象的引用,装饰对象接受所有客户端的请求,并把这些请求转发给真实对象,这样就能在真实对象调用前后增加新的功能。
  4. 装饰对象负责给真实对象增加新的责任。

使用场景:

  1. IO中输入流和输出流的设计
  2. Swing包中图形界面构件功能
  3. Servlet API中提供了一个request对象的包装类设计模式的实现类HttpServletRequestWrapper,该类增强了request对象的功能。
  4. Struts2中,request, response, session对象的处理

创建一个抽象组件ICar接口,并创建具体构建角色以及各个具体装饰角色

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
// 抽象组件 
public interface ICar {
void move();
}

// 具体构建角色() ConcreteComponent
class car implements ICar {
@Override
public void move() {
System.out.println("陆地上跑!");
}
}

class superCar implements ICar {
private ICar car;
public SuperCar(ICar car) {
this.car = car;
}
@Override
public void move() {
car.move();
}
}

// 具体装饰角色 ConcreteDecorator
class FlyCar extends SuperCar {
public FlyCar(ICar car) {
super(car);
}
public void fly() {
System.out.println("天上飞");
}
@Override
public void move() {
super.move;
fly();
}
}

// 具体装饰角色 ConcreteDecorator
class WaterCar extends SuperCar {
public WaterCar(ICar car) {
super(car);
}
public void swim() {
System.out.println("水里游");
}
@Override
public void move() {
super.move;
swim();
}
}

// 具体装饰角色 ConcreteDecorator
class AICar extends SuperCar {
public AICar(ICar car) {
super(car);
}
public void autoMove() {
System.out.println("自动跑");
}
@Override
public void move() {
super.move;
autoMove();
}
}

public class Client {
public static void main(String[] args) {
Car car = new Car();
car.move();

// 新增功能:飞行
ICar flycar = new FlyCar(car);
flycar.move(); // 陆地上跑 天上飞

// 新增功能水里游
ICar watercar = new WaterCar(car);
watercar.move(); // 陆地上跑 水里游

// 新增两个功能 飞行和水里游
ICar car2 = new WaterCar(new FlyCar());
car2.move; // 陆地上跑 天上飞 水里游
}
}

图:

总结

装饰模式降低耦合度,动态的增加或者删除对象的职责,并且让需要装饰的具体的构建类可以独立变化,以便增加新的具体构建类和具体装饰类。

优点:

  • 扩展对象功能,比继承灵活,不会导致类个数几句增加
  • 可以对一个对象进行多次装饰,创造出不同行为的组合
  • 具体构建类和具体装饰类可以独立变化,用户可以根据需要自己增加新的具体构件子类和具体装饰子类。

缺点:

  • 产生了很多小对象,大量小对象占据内存,影响性能
  • 容易出错,调试排查麻烦

装饰模式和桥接模式区别:

两个模式都是为了解决过多子类对象问题,诱因不同,桥接模式是对象自身现有机制沿着多个维度变化,是既有部分不稳定,装饰模式是为了增加新的功能。

单粒设计模式


单粒设计模式的完美实现(静态内部类、双重检查锁)

抽象模板

需求:统计代码块运行时长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class GetTimeTemplate {
// 固定流程方法
public long getTime() {
// 获取起始时间
long t1 = System.currentTimeMillis();

// 模板方法
code();

long t2 = System.currentTimeMillis();
return t2 - t1;
}

// 钩子方法
public abstract void code();
}

子类必须实现钩子方法。才能使用功能。

策略模式


  • 抽象策略模式:通常由一个接口或者抽象类
  • 具体策略角色:包装了相关的算法和行为
  • 环境角色:持有一个策略类的引用

案例演示:

抽象策略类:TravelStrategy
具体的策略:飞机、火车、自行车
拥有策略的容器:人 Person

人要履行,拥有策略类集合(坐飞机、坐火车、骑自行车)

应用:springmvc中的 RequestHandler 和 handler 的映射

适配器模式


作用:将一个类的接口转换成另外一个客户希望的接口

解决的问题就是一个标准和另一个标准不兼容的问题

解决方案有两种:

  1. 一个标准去匹配另一个标准
  2. 两个不兼容的标准,去找到一个兼容的标准。

应用:springmvc 中的 RequestAdapter 针对 Object 类型的处理器做适配。使之可以正常工作

文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/06/18/%E4%BA%8C%E5%8D%81%E5%85%AD%E7%A7%8D%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论