Skip to content

Commit 24826e2

Browse files
authored
Create 设计模式.md
1 parent 69aa733 commit 24826e2

File tree

1 file changed

+289
-0
lines changed

1 file changed

+289
-0
lines changed

docs/设计模式.md

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
2+
3+
有一些重要的设计原则在开篇和大家分享下,这些原则将贯通全文:
4+
5+
1. 面向接口编程,而不是面向实现。这个很重要,也是优雅的、可扩展的代码的第一步,这就不需要多说了吧。
6+
2. 职责单一原则。每个类都应该只有一个单一的功能,并且该功能应该由这个类完全封装起来。
7+
3. 对修改关闭,对扩展开放。对修改关闭是说,我们辛辛苦苦加班写出来的代码,该实现的功能和该修复的 bug 都完成了,别人可不能说改就改;对扩展开放就比较好理解了,也就是说在我们写好的代码基础上,很容易实现扩展。
8+
9+
**创建型模式比较简单,但是会比较没有意思,结构型和行为型比较有意思。**
10+
11+
**每个代理模式的代码都必须自己手动完成一遍。**
12+
13+
14+
15+
## 创建型模式
16+
17+
创建型模式的作用就是创建对象,说到创建一个对象,最熟悉的就是 new 一个对象,然后 set 相关属性。但是,在很多场景下,我们需要给客户端提供更加友好的创建对象的方式,尤其是那种我们定义了类,但是需要提供给其他开发者用的时候。
18+
19+
### 简单工厂模式
20+
21+
```java
22+
public class FoodFactory {
23+
24+
public static Food makeFood(String name) {
25+
if (name.equals("兰州拉面")) {
26+
Food noodle = new LanZhouNoodle();
27+
System.out.println("兰州拉面"+noodle+"出锅啦");
28+
return noodle;
29+
} else if (name.equals("黄焖鸡")) {
30+
Food chicken = new HuangMenChicken();
31+
System.out.println("黄焖鸡"+ chicken +"出锅啦");
32+
return chicken;
33+
} else {
34+
System.out.println("不知道你做的什么哦~");
35+
return null;
36+
}
37+
}
38+
}
39+
```
40+
41+
*其中,LanZhouNoodle 和 HuangMenChicken 都继承自 Food。*
42+
43+
```java
44+
public class Cook {
45+
public static void main(String[] args) {
46+
Food food = FoodFactory.makeFood("黄焖鸡");
47+
FoodFactory.makeFood("jaja");
48+
}
49+
}
50+
```
51+
52+
简单地说,简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。
53+
54+
> 我们强调**职责单一**原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food。
55+
56+
在此例中可以看出,Cook 类在使用 FoodFactory 时就不需要 new 任何一个对象,这就是简单工厂模式的好处,封装了 new 的部分,做到的代码易用性。
57+
58+
### 工厂模式
59+
60+
简单工厂模式很简单,如果它能满足我们的需要,我觉得就不要折腾了。之所以需要引入工厂模式,是因为我们往往需要使用两个或两个以上的工厂。
61+
62+
```java
63+
public interface FoodFactory {
64+
Food makeFood(String name);
65+
}
66+
public class ChineseFoodFactory implements FoodFactory {
67+
68+
@Override
69+
public Food makeFood(String name) {
70+
if (name.equals("A")) {
71+
return new ChineseFoodA();
72+
} else if (name.equals("B")) {
73+
return new ChineseFoodB();
74+
} else {
75+
return null;
76+
}
77+
}
78+
}
79+
public class AmericanFoodFactory implements FoodFactory {
80+
81+
@Override
82+
public Food makeFood(String name) {
83+
if (name.equals("A")) {
84+
return new AmericanFoodA();
85+
} else if (name.equals("B")) {
86+
return new AmericanFoodB();
87+
} else {
88+
return null;
89+
}
90+
}
91+
}
92+
```
93+
94+
其中,ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food。
95+
96+
客户端调用:
97+
98+
```java
99+
public class APP {
100+
public static void main(String[] args) {
101+
// 先选择一个具体的工厂
102+
FoodFactory factory = new ChineseFoodFactory();
103+
// 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
104+
Food food = factory.makeFood("A");
105+
}
106+
}
107+
```
108+
109+
虽然都是调用 makeFood("A") 制作 A 类食物,但是,不同的工厂生产出来的完全不一样。
110+
111+
第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。
112+
113+
**核心在于,我们需要在第一步选好我们需要的工厂**。比如,我们有 LogFactory 接口,实现类有 FileLogFactory 和 KafkaLogFactory,分别对应将日志写入文件和写入 Kafka 中,显然,我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory,这将决定之后的所有的操作。
114+
115+
### 抽象工厂模式
116+
117+
当涉及到**产品族**的时候,就需要引入抽象工厂模式了。 一个经典的例子是造一台电脑 。
118+
119+
当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题。
120+
121+
当然,抽象工厂的问题也是显而易见的,比如我们要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了**对修改关闭,对扩展开放**这个设计原则。
122+
123+
### 单例模式
124+
125+
简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例。
126+
127+
getInstance()的返回值是一个对象的引用,并不是一个新的实例,所以不要错误的理解成多个对象。
128+
129+
**懒汉式写法(线程安全)**
130+
131+
```java
132+
public class Singleton {
133+
private static Singleton instance;
134+
private Singleton (){}
135+
public static synchronized Singleton getInstance() {
136+
if (instance == null) {
137+
instance = new Singleton();
138+
}
139+
return instance;
140+
}
141+
}
142+
```
143+
144+
**饿汉式写法**
145+
146+
```java
147+
public class Singleton {
148+
private static Singleton instance = new Singleton();
149+
private Singleton (){}
150+
public static Singleton getInstance() {
151+
return instance;
152+
}
153+
}
154+
```
155+
156+
弊端:因为类加载的时候就会创建对象,所以有的时候还不需要使用对象,就会创建对象,造成内存的浪费;
157+
158+
**饱汉模式最容易出错:**
159+
160+
```java
161+
public class Singleton {
162+
// 首先,也是先堵死 new Singleton() 这条路
163+
private Singleton() {}
164+
// 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
165+
private static volatile Singleton instance = null;
166+
167+
public static Singleton getInstance() {
168+
if (instance == null) {
169+
// 加锁
170+
synchronized (Singleton.class) {
171+
// 这一次判断也是必须的,不然会有并发问题
172+
if (instance == null) {
173+
instance = new Singleton();
174+
}
175+
}
176+
}
177+
return instance;
178+
}
179+
}
180+
```
181+
182+
> 双重检查,指的是两次检查 instance 是否为 null。
183+
>
184+
> volatile 在这里是需要的,希望能引起读者的关注。
185+
>
186+
> 很多人不知道怎么写,直接就在 getInstance() 方法签名上加上 synchronized,这就不多说了,性能太差。
187+
188+
嵌套类最经典,以后大家就用它吧:
189+
190+
```java
191+
public class Singleton {
192+
193+
private Singleton() {}
194+
// 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
195+
private static class Holder {
196+
private static Singleton instance = new Singleton();
197+
}
198+
public static Singleton getInstance() {
199+
return Holder.instance;
200+
}
201+
}
202+
```
203+
204+
> 注意,很多人都会把这个**嵌套类**说成是**静态内部类**,严格地说,内部类和嵌套类是不一样的,它们能访问的外部类权限也是不一样的。
205+
206+
最后,我们说一下枚举,枚举很特殊,它在类加载的时候会初始化里面的所有的实例,而且 JVM 保证了它们不会再被实例化,所以它天生就是单例的。
207+
208+
**TODO:**
209+
210+
建造者模式
211+
212+
原型模式
213+
214+
## 结构型模式
215+
216+
前面创建型模式介绍了创建对象的一些设计模式,这节介绍的结构型模式旨在通过改变代码结构来达到解耦的目的,使得我们的代码容易维护和扩展。
217+
218+
219+
220+
### 代理模式
221+
222+
第一个要介绍的代理模式是最常使用的模式之一了,用一个代理来隐藏具体实现类的实现细节,通常还用于在真实的实现的前后添加一部分逻辑。
223+
224+
既然说是**代理**,那就要对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,代理只是个代理,它不会完成实际的业务逻辑,而是一层皮而已,但是对于客户端来说,它必须表现得就是客户端需要的真实实现。
225+
226+
理解**代理**这个词,这个模式其实就简单了。 下面上代码理解。
227+
代理接口:
228+
229+
```java
230+
//要有一个代理接口让实现类和代理实现类来实现。
231+
public interface FoodService {
232+
Food makeChicken();
233+
}
234+
```
235+
236+
被代理的实现类:
237+
238+
```java
239+
public class FoodServiceImpl implements FoodService {
240+
@Override
241+
public Food makeChicken() {
242+
Food f = new Chicken();
243+
f.setChicken("1kg");
244+
f.setSpicy("1g");
245+
f.setSalt("3g");
246+
System.out.println("鸡肉加好佐料了");
247+
return f;
248+
}
249+
}
250+
```
251+
252+
被代理实现类就只需要做自己该做的事情就好了,不需要管别的。
253+
254+
代理实现类:
255+
256+
```java
257+
public class FoodServiceProxy implements FoodService {
258+
// 内部一定要有一个真实的实现类,当然也可以通过构造方法注入
259+
private FoodService foodService = new FoodServiceImpl();
260+
261+
@Override
262+
public Food makeChicken() {
263+
System.out.println("开始制作鸡肉");
264+
265+
// 如果我们定义这句为核心代码的话,那么,核心代码是真实实现类做的,
266+
// 代理只是在核心代码前后做些“无足轻重”的事情
267+
Food food = foodService.makeChicken();
268+
269+
System.out.println("鸡肉制作完成啦,加点胡椒粉");
270+
food.addCondiment("pepper");
271+
System.out.println("上锅咯");
272+
return food;
273+
}
274+
}
275+
```
276+
277+
客户端调用,注意,我们要用代理来实例化接口:
278+
279+
```java
280+
// 这里用代理类来实例化
281+
FoodService foodService = new FoodServiceProxy();
282+
foodService.makeChicken();
283+
```
284+
285+
所谓代理模式,就是对被代理方法包装或者叫增强, 在面向切面编程(AOP)中,其实就是动态代理的过程。比如 Spring 中,我们自己不定义代理类,但是 Spring 会帮我们动态来定义代理,然后把我们定义在 @Before@After@Around 中的代码逻辑动态添加到代理中。
286+
287+
288+
289+
待续。。。

0 commit comments

Comments
 (0)