forked from qinxuewu/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
创建型模式.md
410 lines (339 loc) · 14.5 KB
/
创建型模式.md
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
### 单例模式
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
**单例模式的特点:**
1. 单例类只能有一个实例。
1. 单例类必须自己创建自己的唯一实例。
1. 单例类必须给所有其他对象提供这一实例。
单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例
**懒汉式单例类**
- 懒汉式单例类.在第一次调用的时候实例化自己
- 懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间
```
public class Singleton {
private Singleton(){
}
//懒汉式单例类.在第一次调用的时候实例化自己
private static Singleton single=null;
//静态工厂方法 在第一次调用的时候实例化自己
public static Singleton getInstance(){
if(single==null){
single = new Singleton();
}
return single;
}
}
```
**饿汉式单例**
- 饿汉式在类初始化时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
- 饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
```
class Singleton2{
private Singleton2() {}
private static Singleton2 instance=new Singleton2();
public static Singleton2 getInstance2(){
return instance;
}
}
```
**双重检查加锁实现单例模式**
- 线程环境下懒汉式单例模式是线程不安全可能会出现多个实例,可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响
- 所谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
- 双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量
```
//双重锁检查单例模式
class Singleton3{
private Singleton3(){}
private static Singleton3 single=null;
//静态工厂方法 在第一次调用的时候实例化自己
public static Singleton3 getInstance3(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(single==null){
synchronized (Singleton3.class){
//再次判断实例是否存在,不存在 则创建
if(single==null){
single=new Singleton3();
}
}
}
return single;
}
}
```
**Lazy initialization holder class模式**
- 这个模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙地同时实现了延迟加载和线程安全。
**什么是类级内部类?**
1. 类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
1. 类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
1. 类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
1. 类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载。
**多线程缺省同步锁的知识**
- 在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。
**这些情况包括:**
1. 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
1. 访问final字段时
1. 创建线程之前创建对象时
1. 线程可以看见它将要处理的对象时
**有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象实例,从而同时实现延迟加载和线程安全。**
```
public class Singleton4 {
private Singleton4(){}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
*/
private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static Singleton4 instance = new Singleton4();
}
public static Singleton4 getInstance(){
return SingletonHolder.instance;
}
}
```
当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
### 单例和枚举
使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
```
public class DataSource {
}
public enum SingletonEnum {
/**
* 定义一个枚举元素,代表了Singleton的一个实例
*/
uniqueInstance;
private DataSource data=null;
private SingletonEnum() {
data = new DataSource();
}
public DataSource getConnection() {
return data;
}
}
public class SingletonEnumTest {
public static void main(String[] args) {
DataSource d=new DataSource();
DataSource d2=new DataSource();
System.out.println("使用枚举单列前------------------");
System.out.println(d==d2);
DataSource d3=SingletonEnum.uniqueInstance.getConnection();
DataSource d4=SingletonEnum.uniqueInstance.getConnection();
ExecutorService exec=Executors.newCachedThreadPool();
for (int i = 0; i <10 ; i++) {
exec.execute(new Runnable() {
@Override
public void run() {
DataSource d3=SingletonEnum.uniqueInstance.getConnection();
DataSource d4=SingletonEnum.uniqueInstance.getConnection();
System.out.println("枚举单列模式使用-----");
System.out.println(d3==d4);
}
});
}
}
}
```
### 抽象工厂模式
**场景问题**
- 活中常见的例子——组装电脑,我们在组装电脑的时候,通常需要选择一系列的配件,比如CPU、硬盘、内存、主板、电源、机箱等,事实上,在选择CPU的时候,面临一系列的问题,比如品牌、型号、针脚数目、主频等问题,只有把这些问题都确定下来,才能确定具体的CPU。同样,在选择主板的时候也是如此。选择不同的CPU和主板,是每个客户在组装电脑的时候,向装机公司提出的要求,也就是我们每个人自己拟定的装机方案。在最终确定这个装机方案之前,还需要整体考虑各个配件之间的兼容性。比如:CPU和主板,如果使用Intel的CPU和AMD的主板是根本无法组装。所以装机方案是整体性的,里面选择的各个配件之间是有关联的。
对于装机工程师而言,他只知道组装一台电脑,需要相应的配件,但是具体使用什么样的配件,还得由客户说了算。也就是说装机工程师只是负责组装,而客户负责选择装配所需要的具体的配件。因此,当装机工程师为不同的客户组装电脑时,只需要根据客户的装机方案,去获取相应的配件,然后组装即可。
```
/**
* CPU接口与具体实现
* @author qinxuewu
* @create 18/7/15下午4:01
* @since 1.0.0
*/
public interface Cpu {
public void calculate();
}
/**
* 〈不同型号cpu的实现类〉
* @author qinxuewu
* @create 18/7/15下午4:01
* @since 1.0.0
*/
public class IntelCpu implements Cpu {
/**
* CPU的针脚数
*/
private int pins = 0;
public IntelCpu(int pins){
this.pins = pins;
}
@Override
public void calculate() {
System.out.println("Intel CPU的针脚数:" + pins);
}
}
public class AmdCpu implements Cpu {
/**
* CPU的针脚数
*/
private int pins = 0;
public AmdCpu(int pins){
this.pins = pins;
}
@Override
public void calculate() {
System.out.println("Amd CPU的针脚数:" + pins);
}
}
```
主板接口与具体实现
```
/**
* 〈主板公共接口〉
* @author qinxuewu
* @create 18/7/15下午4:05
* @since 1.0.0
*/
public interface Mainboard {
public void installCPU();
}
public class IntelMainboard implements Mainboard{
/**
* CPU插槽的孔数
*/
private int cpuHoles = 0;
/**
* 构造方法,传入CPU插槽的孔数
* @param cpuHoles
*/
public IntelMainboard(int cpuHoles){
this.cpuHoles = cpuHoles;
}
@Override
public void installCPU() {
System.out.println("Intel主板的CPU插槽孔数是:" + cpuHoles);
}
}
public class AmdMainboard implements Mainboard{
/**
* CPU插槽的孔数
*/
private int cpuHoles = 0;
/**
* 构造方法,传入CPU插槽的孔数
* @param cpuHoles
*/
public AmdMainboard(int cpuHoles){
this.cpuHoles = cpuHoles;
}
@Override
public void installCPU() {
System.out.println("Amd 主板的CPU插槽孔数是:" + cpuHoles);
}
}
```
CPU与主板工厂类
```
public class CpuFactory {
public static Cpu createCpu(int type){
Cpu cpu=null;
if(type == 1){
cpu = new IntelCpu(755);
}else if(type == 2){
cpu = new AmdCpu(938);
}
return cpu;
}
}
public class MainboardFactory {
public static Mainboard createMainboard(int type){
Mainboard mainboard = null;
if(type == 1){
mainboard = new IntelMainboard(755);
}else if(type == 2){
mainboard = new AmdMainboard(938);
}
return mainboard;
}
}
```
抽象工厂类和实现类
```
/**
* 〈抽象工厂类〉
*/
public interface AbstractFactory {
/**
* 创建CPU对象
* @return CPU对象
*/
public Cpu createCpu();
/**
* 创建主板对象
* @return 主板对象
*/
public Mainboard createMainboard();
}
public class IntelFactory implements AbstractFactory {
@Override
public Cpu createCpu() {
return new IntelCpu(755);
}
@Override
public Mainboard createMainboard() {
return new IntelMainboard(755);
}
}
public class AmdFactory implements AbstractFactory {
@Override
public Cpu createCpu() {
return new IntelCpu(938);
}
@Override
public Mainboard createMainboard() {
return new IntelMainboard(938);
}
}
```
装机工程师类与客户类
```
public class ComputerEngineer {
private Cpu cpu=null;
private Mainboard mainboard=null;
/**
* 根据传入的主板型号和cpu型号 进行组装电脑
*/
public void makeComputer(int cpuType,int mainboard){
/**
* 组装机器的基本步骤
*/
//1:首先准备好装机所需要的配件
//直接找相应的工厂获取
this.cpu = af.createCpu();
this.mainboard = af.createMainboard();
//测试配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
//2:组装机器
//3:测试机器
//4:交付客户
}
}
public class Client {
public static void main(String[] args) {
ComputerEngineer cf = new ComputerEngineer();
//客户选择并创建需要使用的产品对象
AbstractFactory af = new IntelFactory();
//告诉装机工程师自己选择的产品,让装机工程师组装电脑
cf.makeComputer(af);
}
}
```
**抽象工厂模式的优点**
分离接口和实现
- 客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已。也就是说,客户端从具体的产品实现中解耦。
使切换产品族变得容易
- 因为一个具体的工厂实现代表的是一个产品族,比如上面例子的从Intel系列到AMD系列只需要切换一下具体工厂。
**抽象工厂模式的缺点**
- 不太容易扩展新的产品,如果需要给整个产品族添加一个新的产品,那么就需要修改抽象工厂,这样就会导致修改所有的工厂实现类。
### [工厂方法](http://www.cnblogs.com/java-my-life/archive/2012/03/28/2418836.html)
### [建造模式](http://www.cnblogs.com/java-my-life/archive/2012/04/07/2433939.html)
建造模式是对象的创建模式。建造模式可以将一个产品的内部表象与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。
### [原型模式](http://www.cnblogs.com/java-my-life/archive/2012/04/11/2439387.html)
- 原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。这就是选型模式的用意。