JAVA设置模式学习笔记

风尘

文章目录

  1. 1. 遵循原则
    1. 1.1. 开闭原则
    2. 1.2. 里氏替换原则:
    3. 1.3. 依赖倒置原则:
    4. 1.4. 单一职责原则:
    5. 1.5. 接口隔离原则
    6. 1.6. 迪米特法则
    7. 1.7. 合成复用原则
    8. 1.8. Java中的常见规则:
  2. 2. 设计模式
    1. 2.1. 单例模式
      1. 2.1.1. 懒汉模式:
      2. 2.1.2. 饿汉模式:
    2. 2.2. 工厂方法模式
    3. 2.3. 抽象工厂模式
    4. 2.4. 代理模式
    5. 2.5. 建造者模式
    6. 2.6. 原型模式
    7. 2.7. 观察者模式
    8. 2.8. 适配器模式

[TOC]

遵循原则

开闭原则

一个软件实体(类、模块、函数)应该对外扩展开放,对修改关闭。
运维尽量减少对原代码的修改,保持历史代码的纯洁性,提高系统的稳定性;

里氏替换原则:

里氏替换原则是实现开闭原则的重要方式之一。
子类可以扩展父类的功能,但不能改变父类原有的功能。
即,子类继承父类时,除了添加新方法外,尽量不要重写父类方法。

依赖倒置原则:

依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
高层模块不应该依赖底层模块,两者都应该依赖其抽象类;
抽象不应该依赖细节;细节应该依赖抽象。
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性。

单一职责原则:

一个类或接口只有一个职责;
应该仅有一个原因引起类的变更,否则类应该被拆分;

在日常使用中很难完全做到。
提高了可读性和可维护性,降低变更引起的风险。

接口隔离原则

尽量将臃肿庞大的接口拆分成更小的和更具体的接口,但是要有限度;
要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

迪米特法则

核心观念是类间解耦,弱耦合;
如果两个实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

合成复用原则

在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
如果要使用继承关系,则必须严格遵循里氏替换原则。

Java中的常见规则:

  • 接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化。

  • 各个类或模块的实现彼此独立,不相互影响,实现模块间的松耦合。

  • 接口尽可能小,一个接口只服务于一个子模块或业务逻辑

  • 已经被污染的接口,若变更的风险较大,则采用适配器模式进行处理。

设计模式

单例模式

定义:确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

使用场景
1、要求生成唯一序列号的环境;
2、整个项目需要一个共享访问点或共享数据。
3、创建对象需要消耗的资源过多,如访问IO和数据库等资源。

懒汉模式:

该模式在类加载时没有生成实例,只有当第一次调用 getInstance 方法时才去创建这个实例。

示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SingletonClass1 {

private static volatile SingletonClass1 instance = null;

private SingletonClass1(){}

public static synchronized SingletonClass1 getInstance(){

return instance;

}

}

饿汉模式:

该模式在类加载就创建一个实例,保证在调用 getInstance 方法之前实例已经存在。

示例代码
1
2
3
4
5
6
7
8
9
10
11
public class SingletonClass2 {

private static final SingletonClass2 instance = new SingletonClass2();

private SingletonClass2(){}

public static SingletonClass2 getInstance(){
return instance;
}

}

工厂方法模式

定义: 定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使用一个类的实例化延迟到其子类。

工厂方法模式的优点

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。简单工厂模式缺点是增加新产品时会违背“开闭原则”。

简单工厂模式简单工厂模式
示例代码
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
public interface Product {
void create();
}

public class ConcreteProductA implements Product{
@Override
public void create() {
// TODO:
System.out.println("简单工厂模式创建实际产品A!");
}
}

public class ConcreteProductB implements Product{
@Override
public void create() {
// TODO:
System.out.println("简单工厂模式创建实际产品B!");
}
}

public class ProductFactory {
public static <T> T getProduct(Class c) {
Product product = null;

try {
product = (Product) Class.forName(c.getName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}

return (T)product;
}
}

public class App {
public static void main(String[] args) {
Product product = ProductFactory.getProduct(ConcreteProductA.class);
product.create(); // 打印“简单工厂模式创建实际产品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
// -------->在简单工厂模式基本上增加各产品对象工作
/**
* 产品创建工厂接口
*
* @author Windus
* @date
*/
public interface CreateFactory {
Product createProduct();
}

/**
* 产品 A 创建工厂
* @date
* @author Windus
*/
public class ProductAFactory implements CreateFactory{
@Override
public Product createProduct() {
return new ConcreteProductA();
}
}

/**
* 产品 B 创建工厂
* @date
* @author Windus
*/
public class ProductBFactory implements CreateFactory{
@Override
public Product createProduct() {
return new ConcreteProductB();
}
}

// -------->修改 app 类调用
/**
* @author Windus
* @date
*/
public class App {
public static void main(String[] args) {
// 创建产品 A
CreateFactory factoryA = new ProductAFactory();
Product productA = factoryA.createProduct();
productA.create();

// 创建产品 B
CreateFactory factoryB = new ProductBFactory();
Product productB = factoryB.createProduct();
productB.create();
}
}

抽象工厂模式

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点有

  • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  • 当增加一个新的产品族时不需要修改原代码,满足开闭原则。
抽象工厂模式抽象工厂模式
示例代码
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
public interface ProductA {
void create();
}

public interface ProductB {
void create();
}

public class ConcreteProductAA implements ProductA{
@Override
public void create() {
System.out.println("抽象工厂 A 创建实际产品 A!");
}
}

public class ConcreteProductAB implements ProductB{
@Override
public void create() {
System.out.println("抽象工厂 A 创建实际产品 B!");
}
}

public class ConcreteProductBA implements ProductA{
@Override
public void create() {
System.out.println("抽象工厂 B 创建实际产品 A!");
}
}

public class ConcreteProductBB implements ProductB{
@Override
public void create() {
System.out.println("抽象工厂 B 创建实际产品 B!");
}
}

// 抽象工厂
public interface ProductFactory {
ProductA createProductA();
ProductB createProductB();
}

public class ConcreteProductFactoryA implements ProductFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductAA();
}

@Override
public ProductB createProductB() {
return new ConcreteProductAB();
}
}

public class ConcreteProductFactoryB implements ProductFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductBA();
}

@Override
public ProductB createProductB() {
return new ConcreteProductBB();
}
}

public class App {
public static void main(String[] args) {
ProductFactory factoryA = new ConcreteProductFactoryA();
ProductA productAA = factoryA.createProductA();
ProductB productAB = factoryA.createProductB();
productAA.create();
productAB.create();
System.out.println("---------------------------------------------");
ProductFactory factoryB = new ConcreteProductFactoryB();
ProductA productBA = factoryB.createProductA();
ProductB productBB = factoryB.createProductB();
productBA.create();
productBB.create();
}
}

运行结果:
抽象工厂 A 创建实际产品 A!
抽象工厂 A 创建实际产品 B!
---------------------------------------------
抽象工厂 B 创建实际产品 A!
抽象工厂 B 创建实际产品 B!
  1. 当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
  2. 当产品族中需要增加一个新种类的产品时,则所有的工厂类都需要进行修改,不满足开闭原则。

代理模式

定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问。 由于访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
优点

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

普通代理:通过代理来访问角色,不能直接访问真实角色;真实角色的初始化放在代理类中。

普通代理模式普通代理模式
示例代码
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
public interface Game {
void play();
}

public class Contra implements Game{
@Override
public void play() {
System.out.println("魂斗罗游戏 开始!");
}
}

public class ContraProxy implements Game{
Contra contra;

public ContraProxy(Contra contra) {
this.contra = contra;
}

@Override
public void play() {
prePlay();
contra.play();
afterPlay();
}

public void prePlay(){
System.out.println("玩魂斗罗游戏 开始前!");
}

public void afterPlay(){
System.out.println("玩魂斗罗游戏 结束后!");
}
}

public class App {
public static void main(String[] args) {
Contra contra = new Contra();
Game game = new ContraProxy(contra);
game.play();
}
}

强制代理:真实角色初始化后,不能直接使用,必须获取代理类,用代理类调用对应的方法。
强制代理通常通过增加getProxy()方法实现获取方法,在真实类增加私有方法判断是否通过代理类调用。

代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

动态代理:在实现阶段不用关心代理谁,在运行阶段才指定代理那个对象。
静态代理中(普通代理和强制代理),每一个代理类只能为一个接口服务,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时导致代码是重复代码,可以通过动态代理解决这个问题。

动态代理通过调用Proxy的静态方法执行实现InvocationHandler接口的类实现动态创建代理。

动态代理模式动态代理模式
示例代码
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
// 增加一款新游戏
public class KingofGlory implements Game{
@Override
public void play() {
System.out.println("王者荣耀 开始!");
}
}

// 增加游戏媒介类(小孩玩游戏)
public class Kid implements InvocationHandler {

private Object game;

public Kid(Object game) {
this.game = game;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
prePlay();
method.invoke(game, args);
afterPlay();
return null;
}

public void prePlay() {
System.out.println("玩游戏 开始前!");
}

public void afterPlay() {
System.out.println("玩游戏 结束后!");
}
}

// 动态代理实现
public class App {
public static void main(String[] args) {
// 玩魂斗罗
Contra contra = new Contra();
InvocationHandler invocationHandler1 = new Kid(contra);
Game gameDynamicProxy1 = (Game) Proxy.newProxyInstance(Contra.class.getClassLoader(),
Contra.class.getInterfaces(), invocationHandler1);

gameDynamicProxy1.play();

// 玩王者荣耀
KingofGlory kingofGlory = new KingofGlory();
InvocationHandler invocationHandler2 = new Kid(kingofGlory);
Game gameDynamicProxy2 = (Game) Proxy.newProxyInstance(KingofGlory.class.getClassLoader(),
KingofGlory.class.getInterfaces(), invocationHandler2);

gameDynamicProxy2.play();
}
}

建造者模式

将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。如将电子邮件对象分解成发件人、收件人、主题、内容、附件等内容对象。

优点

  • 各个具体的建造者相互独立,有利于系统的扩展。
  • 客户端不必知道产品内部组成的细节,便于控制细节风险。

缺点

  • 产品的组成部分必须相同,这限制了其使用范围。
  • 如果产品的内部变化复杂,该模式会增加很多的建造者类。
建造者模式建造者模式
示例代码
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
103
104
105
106
107
108
109
110
111
112
113
@Data
public class Email {
/**
* 收件人
*/
private String receiver;

/**
* 发件人
*/
private String addresser;

/**
* 主题
*/
private String subject;

/**
* 内容
*/
private String content;

/**
* 附件
*/
private String attachment;

}

/**
* 建造者
*
* @author Windus
*/
public interface Builder {

Email receiveEmail();

void buildReceiver();

void buildAddresser();

void buildSubject();

void buildContent();

void buildAttachment();
}

/**
* 实际建造者
*
* @author Windus
*/
public class ConcreteBuilder implements Builder {

private Email email = new Email();

@Override
public Email receiveEmail() {
return email;
}

@Override
public void buildReceiver() {
this.email.setReceiver("建造发送者!");
System.out.println("建造发送者!");
}

@Override
public void buildAddresser() {
this.email.setAddresser("建造收件者!");
System.out.println("建造收件者!");
}

@Override
public void buildSubject() {
this.email.setSubject("建造主题!");
System.out.println("建造主题!");
}
}

/**
* 指挥者
*
* @author Windus
*/
public class Director {
private Builder builder;

public Director(Builder builder) {
this.builder = builder;
}

public void send() {
builder.buildReceiver();
builder.buildAddresser();
builder.buildSubject();
builder.buildContent();
builder.buildAttachment();
}
}

public class App {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);

director.send();

System.out.println(builder.receiveEmail().toString());
}
}

原型模式

定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。通过对象 clone() 方法实现。

使用场景
1、资源优化场景:类初始化需要消耗非常多的资源。
2、性能和安全要求的场景:通过new产生对象需要繁琐的数据准备或访问权限。
3、一个对象多个修改者场景。

观察者模式

定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象会得到通知并被自动更新。这种模式有时又称作发布-订阅模式。
该模式主要角色有:

  • Subject 抽象目标类,即被观察者角色。提供用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  • Concrete Subject 具体目标类。它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  • Observer 抽象观察者。包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  • Concrete Observer 具体观察者。实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

使用场景
1、关联行为场景;
2、事件多级触发场景;
3、跨系统的消息交换场景,如消息队列的处理机制。

优点

  • 将被观察者和观察者进行解耦,使得他们之间的依赖性更小,甚至做到毫无依赖

缺点

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
观察者模式观察者模式
示例代码
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/**
* 彩票
*
* @author Windus
*/
@Data
public class Lottery {
/**
* 号码
*/
private String no;

/**
* 期号
*/
private String issue;

public Lottery(String no, String issue) {
this.no = no;
this.issue = issue;
}
}

/**
* 被观察者(抽象目标类)
*
* @author Windus
*/
public interface Subject {

/**
* 增加观察者
*
* @param observer
* @return
* @author Windus
*/
void addObserver(Observer observer);

/**
* 删除观察者
*
* @param observer
* @return
* @author Windus
*/
void delObserver(Observer observer);

/**
* 通知观察者
*
* @param lottery
* @return
* @author Windus
*/
void notifyObservers(Lottery lottery);

/**
* 开奖
*
* @param lottery
* @return
* @author Windus
*/
void lottery(Lottery lottery);
}

/**
* 彩票管理中心(具体目标类)
*
* @author Windus
*/
public class LotteryCenter implements Subject {
private List<Observer> observerList = new ArrayList<>();

@Override
public void addObserver(Observer observer) {
this.observerList.add(observer);
}

@Override
public void delObserver(Observer observer) {
this.observerList.remove(observer);
}

@Override
public void notifyObservers(Lottery lottery) {
for (Observer observer : observerList) {
observer.lottery(lottery);
}
}

@Override
public void lottery(Lottery lottery) {
notifyObservers(lottery);
}
}

/**
* 抽象观察者
*
* @author Windus
*/
public interface Observer {

/**
* 开奖
*
* @param lottery
* @return
* @author Windus
*/
void lottery(Lottery lottery);
}

/**
* 具体观察者 A
*
* @author Windus
*/
public class LotteryHoldersA implements Observer {
@Override
public void lottery(Lottery lottery) {
System.out.println("LotteryHoldersA 接收到开奖通知:" + lottery.toString());
}
}

/**
* 具体观察者 B
*
* @author Windus
*/
public class LotteryHoldersB implements Observer {
@Override
public void lottery(Lottery lottery) {
System.out.println("LotteryHoldersB 接收到开奖通知:" + lottery.toString());
}
}

/**
* 模式应用测试类
*
* @author Windus
*/
public class App {
public static void main(String[] args) {
Subject subject = new LotteryCenter();
Observer obsA = new LotteryHoldersA();
Observer obsB = new LotteryHoldersB();

// 添加观察者
subject.addObserver(obsA);
subject.addObserver(obsB);

// 中奖号码
Lottery lottery = new Lottery("82 43 23 32 67 75 07 92", "202005");

// 开奖
subject.lottery(lottery);
}
}

适配器模式

定义:将一个类的接口变成客户端所期待的另一种接口,从而使原本不匹配而无法在一起工作的两个类一起工作。通常包含三个角色:Target目标角色、Adaptee适配者角色、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
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
/**
* 游戏手柄(Target 目标角色)
*
* @author Windus
*/
public interface GamePad {
void control();
}

/**
* GTA 游戏
*
* @author Windus
*/
public class GTAGame {
private GamePad gamePad;

public GTAGame(GamePad gamePad) {
this.gamePad = gamePad;
}

public void start() {
gamePad.control();
}
}

/**
* xbox 游戏手柄
*
* @author Windus
*/
public class XBoxPad implements GamePad {

@Override
public void control() {
System.out.println("xbox 游戏开始!");
}
}

/**
* 特殊游戏手柄(Adaptee 适配者角色)
*
* @author Windus
*/
public interface SpecialGamePad {
void specialControl();
}

/**
* BT 游戏手柄
*
* @author Windus
*/
public class BTPad implements SpecialGamePad {
@Override
public void specialControl() {
System.out.println("BT 游戏开始!");
}
}

/**
* 游戏手柄适配器(Adapter 适配器角色)
* @author Windus
*/
public class GamePadAdapter implements GamePad {

private SpecialGamePad specialGamePad;

public GamePadAdapter(SpecialGamePad specialGamePad) {
this.specialGamePad = specialGamePad;
}

@Override
public void control() {
specialGamePad.specialControl();
}
}

/**
* 模式应用(客户端角色)
* @author Windus
*/
public class App {
public static void main(String[] args) {
// 原手柄调用 xbox
GamePad gamePad = new XBoxPad();
GTAGame gtaGame = new GTAGame(gamePad);
gtaGame.start();

// 适配其他手柄后调用 bt
SpecialGamePad btPad = new BTPad();
GamePadAdapter gamePadAdapter = new GamePadAdapter(btPad);
gtaGame = new GTAGame(gamePadAdapter);
gtaGame.start();
}
}