抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

创建型模式

单例模式

单例其实就是相对于系统来说唯一的一个存在。这里引用百度百科中对单例的解释:“是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。” 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的设计规则

由定义我们可以很清晰的抽象出: 实现Java单例模式类有哪些通用设计规则?

(1)私有化类构造器。

(2)定义静态私有的类对象。

(3)提供公共静态的获取该私有类对象的方法。

了解了单例模式的概念,以及单例模式的通用设计规则,对于如何实现一个Java单例,应该是没什么阻碍了。这里我们还是要思考下单例模式的优点,或者说有啥好处,使用场景是什么?带着这些问题我们就能更好的设计单例模式。

为什么使用单例

1.Java单例模式解决了什么问题?

答:Java的单例模式主要解决了多线程并发访问共享资源的线程安全问题。

2.Java单例模式主要应用场景有哪些?

答:1.共享资源的访问与操作场景,如Windows系统的资源管理器,Windows系统的回收站, 显卡的驱动程序,系统的配置文件,工厂本身(类模板),应用程序的日志对象等。

​ 2.控制资源访问与操作的场景,如数据库的连接池,Java的线程池等。

单例模式的设计

单例模式的命名

单例的命名通常包含 singleton(以 singleton 开头或结尾) 或能按名称实际意义区分出在应用中唯一。

了解了Java单例模式出现的缘由以及出现的场合,那么,Java的单例究竟是以怎样的方式出现在这些场合中呢? Java中单例模式的常用实现方式有哪些?

实现

我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。

SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,我们的演示类使用 SingleObject 类来获取 SingleObject 对象。

单例模式的 UML 图

饿汉式

饿汉式,顾名思义,就是指在JVM首次访问到该单例类时,就会把该单例类对象创建出来,并保存在内存中,不管后续是否会使用到这个单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 饿汉式
* 优点:
* (1) 没有加任何的锁,执行效率较高
* (2) 线程安全
* 缺点:
* (1) 类加载的时候就进行了初始化,程序后续未必会使用到该实例,导致内存浪费
* (2) 反射可破坏单例
*/
public class HungrySingleton {

/** 私有化类构造器 */
private HungrySingleton() {}

/** 定义静态私有类对象 */
private static HungrySingleton instance = new HungrySingleton();

/** 提供公共静态的获取该私有类对象的方法 */
public static HungrySingleton getInstance() {
return instance;
}

}

懒汉式(4种)

懒汉式,相比于饿汉式,它是指JVM首次访问到该单例类时,并不会实例化该单例类对象,只有等到后续被外部调用时,才会实例化该单例类对象。 饿汉式的写法比较固定,懒汉式由于延时加载的特性,写法上有一些变化,一般来说,懒汉式存在4个变种。

1、懒汉式1——无锁懒汉式

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) 该种实现方式存在线程不安全问题
* (2) 反序列化,反射与克隆可破坏单例
*
* 这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
*/
public class LazySingletonWithoutSync {

/** 私有化类构造器 */
private LazySingletonWithoutSync() {}

/** 定义静态私有类对象 */
private static LazySingletonWithoutSync instance;

/** 提供公共静态的获取该私有类对象的方法 */
public static LazySingletonWithoutSync getInstance() {
if (instance == null) {
instance = new LazySingletonWithoutSync();
}

return instance;
}

}

2、懒汉式2——Sync同步锁懒汉式

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
/**
* 懒汉式
* 优点:
* (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
* (2) 线程安全
* 缺点:
* (1) 给获取实例的公共方法加上同步锁synchronized,性能受到影响
* (2) 反序列化,反射与克隆可破坏单例
*/
public class LazySingletonWithSync {

/** 私有化类构造器 */
private LazySingletonWithSync() {}

/** 定义静态私有类对象 */
private static LazySingletonWithSync instance;

/** 提供公共静态的获取该私有类对象的方法 */
public static synchronized LazySingletonWithSync getInstance() {
if (instance == null) {
instance = new LazySingletonWithSync();
}

return instance;
}

}

3、懒汉式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
/**
* 懒汉式——双重锁检查单例 这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
* 优点:
* (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
* (2) 线程安全
* 缺点:
* (1) 如果不加volatile关键词防止指令重排,双重锁检查单例可能会出现不完整实例
* 分析:instance = new LazySingletonWithDoubleCheck() 操作并非原子操作,它包含如下三个操作指令:
* 1) 分配对象的内存空间 memory = allocate()
* 2) 初始化对象 ctorInstance(memory)
* 3) 设置instance指向刚分配的内存地址 instance = memory
* 经过指令重排序后,执行顺序可能如下:
* 1) 分配对象的内存空间 memory = allocate()
* 2) 设置instance指向刚分配的内存地址 instance = memory
* 3) 初始化对象 ctorInstance(memory)
* 若有A线程执行完上述重排序后的第二步,尚未初始化对象,此时B线程来获取单例instance,会发现instance不为空,于是返回该值,但实际该instance尚未构建完成,为不完整实例。
* (2) 反序列化,反射与克隆可破坏单例
*/
public class LazySingletonWithDoubleCheck {

/** 私有化类构造器 */
private LazySingletonWithDoubleCheck() {}

/** 定义静态私有类对象 */
private static volatile LazySingletonWithDoubleCheck instance;

/** 提供公共静态的获取该私有类对象的方法 */
public static LazySingletonWithDoubleCheck getInstance() {
if (instance == null) {
synchronized (LazySingletonWithDoubleCheck.class) {
if (instance == null) {
instance = new LazySingletonWithDoubleCheck();
}
}
}

return instance;
}

}

4、懒汉式4——静态内部类

静态内部类和非静态内部类一样,都是在被调用时才会被加载,以此来实现懒汉模式。所用环境,在单例对象占用资源大,需要延时加载的情况下优选。

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
/**
* 懒汉式——静态内部类单例
* 优点:
* (1) 由于懒汉式延时加载特性,使用该实例时才实例化,节省了内存资源
* (2) 在外部类 getInstance 方法被调用的时候内部类才会被加载,巧妙地避免了线程安 全问题
* (3) 兼顾了synchronized的性能问题
*
* 缺点:
* 反序列化,反射与克隆可破坏单例
*/
public class LazySingletonWithInnerClass {

/** 私有化类构造器 */
private LazySingletonWithInnerClass() {}

/** 使用内部类定义静态私有 LazySingletonWithInnerClass 类对象 */
private static class LazyHolder {
private static final LazySingletonWithInnerClass INSTANCE = new LazySingletonWithInnerClass();
}

/** 提供公共静态的获取该私有类对象的方法 */
public static LazySingletonWithInnerClass getInstance() {
return LazyHolder.INSTANCE;
}

}

注册登记式

每使用一次,都往一个固定的容器中去注册并将使用过的对象进行缓存,下次去取对象的时候,就直接从缓存中取值,以保证每次获取的都是同一个对象。Spring IOC中的单例模式,就是典型的注册登记式单例。

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
/**
* 注册登记式——map容器单例
*/
public class RegisterSingletonFromMap {

/** 私有化类构造器 */
private RegisterSingletonFromMap() {}

/** 使用 ConcurrentHashMap 容器,装载 RegisterSingletonFromMap 类对象 */
private static Map<String, RegisterSingletonFromMap> map = new ConcurrentHashMap<String, RegisterSingletonFromMap>();

/** 提供公共静态的获取该私有类对象的方法 */
public static RegisterSingletonFromMap getInstance() {
String className = RegisterSingletonFromMap.class.getName();

synchronized (RegisterSingletonFromMap.class) {
if (!map.containsKey(className)) {
//ConcurrentMap提供线程安全和原子性保证
map.put(className, new RegisterSingletonFromMap());
}
}

return map.get(className);
}

}

枚举型单例

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
/** 
* 注册登记式还有一种写法,枚举型单例
* 枚举单例
* 这种形式不能防止反射破坏单例,因为构造器还是暴露在枚举外面
*/
public class RegisterSingletonFromEnum {

/** 私有化类构造器 */
private RegisterSingletonFromEnum() {}

/** 使用 enum 实例特性创建 RegisterSingletonFromEnum 类对象 */
private enum Singleton {
INSTANCE;

private RegisterSingletonFromEnum instance;

// JVM保证此方法只调用一次
Singleton() {
instance = new RegisterSingletonFromEnum();
}
}

/** 提供公共静态的获取该私有类对象的方法 */
public static RegisterSingletonFromEnum getInstance() {
return Singleton.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
/**
* 这种方式可以防止反射破坏单例,推荐这个,上面不够准确
*/
public class SingletonTest08 {
public static void main(String[] args) {

Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance == instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());

instance.sayOK();
}
}

//使用枚举,可以实现单例, 推荐
enum Singleton {
INSTANCE; //属性

Singleton(){
//默认构造器,只执行一次
}
public void sayOK() {
System.out.println("ok~");
}
}

序列化与反序列化式

序列化与反序列化式单例,需要重写readResolve()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 序列化与反序列化式单例
*/
public class SerializableSingleton implements Serializable {

/** 私有化类构造器 */
private SerializableSingleton() {}

/** 定义静态私有类对象 */
private static final SerializableSingleton singleton = new SerializableSingleton();

/** 提供公共静态的获取该私有类对象的方法 */
public static SerializableSingleton getInstance() {
return singleton;
}

/** 重写readResolve()方法,保证反序列化生成对象时获得的是同一个对象 */
private Object readResolve() throws ObjectStreamException {
return singleton;
}

}

单例模式要点总结

Singleton 模式中的实例构造器可以设置为 protected 以允许子类派生。

Singleton 模式一般不要实现 Clone 接口,因为这有可能导致多个对象实例,与 Singleton 模式的初衷违背。

如何实现多线程环境下安全的 Singleton? 需注意对双检查锁的正确实现。


工厂模式

​ 工厂方法模式是对简单工厂的一个衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。 说了这么多我们先来看看什么情况下使用工厂模式。

应用场景

第一种情况是对于某个产品,调用者清楚地知道应该使用哪个具体工厂服务,实例化该具体工厂,生产出具体的产品来。Java Collection中的iterator() 方法即属于这种情况。

第二种情况,只是需要一种产品,而不想知道也不需要知道究竟是哪个工厂为生产的,即最终选用哪个具体工厂的决定权在生产者一方,它们根据当前系统的情况来实例化一个具体的工厂返回给使用者,而这个决策过程这对于使用者来说是透明的。

工厂方法模式是在简单工厂模式的衍生,所以我们先来了解下什么是简单工厂。


简单工厂模式

1、首先先创建一个car的抽象类定义一个run方法

1
2
3
4
public interface Car {

public void run();
}

2、创建实现类 具体的两个产品

宝马车实现类

1
2
3
4
5
6
7
public class BMWCar implements Car {


public void run() {
System.out.println("bao ma car is running");
}
}

路虎车实现类

1
2
3
4
5
6
7
public class LandRoverCar implements Car {

public void run() {

System.out.println("LandRover car is runnning ");
}
}

3、创建汽车工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CarFactory {

public static Car createBMCar()
{
return new BMWCar();

}

public static Car createLandRoverCar()
{
return new LandRoverCar();

}
}

4、测试

1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
Car car = CarFactory.createBMCar();
car.run();
}
}

测试结果 : bao ma car is running

总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。但是同时简单工厂存在于这几个缺点。

缺点:

  • 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
  • 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂
  • 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。

工厂方法模式

解决了什么问题

简单工厂模式存在着不易扩展,违背开闭原则的致命缺点。所以工厂方法就是在简单工厂的模式上解决它不易于扩展的缺点

工厂方法的实现

  • 首先先创建一个car的抽象类定义一个run方法
1
2
3
4
public interface Car {

public void run();
}
  • 创建实现类 具体的两个产品

宝马车实现类

1
2
3
4
5
6
public class BMWCar implements Car {

public void run() {
System.out.println("bao ma car is running");
}
}

路虎车实现类

1
2
3
4
5
6
public class LandRoverCar implements Car {

public void run() {
System.out.println("LandRover car is runnning ");
}
}
  • 抽象工厂
1
2
3
4
public interface CarFactory {

public Car createCar();
}
  • 工厂实现类

宝马工厂

1
2
3
4
5
6
public class BMWCarFactory implements CarFactory {

public Car createCar() {
return new BMWCar();
}
}

路虎工厂

1
2
3
4
5
6
public class LandRoverCarFactory implements CarFactory {

public Car createCar() {
return new LandRoverCar();
}
}
  • 测试
1
2
3
4
5
6
7
8
public class Consumer {
public static void main(String[] args) {
Car car1 = new BMWCarFactory().createCar();
Car car2 = new LandRoverCarFactory().createCar();
car1.run();
car2.run();
}
}

image-20200703225102977

总结:

这里这样设计的好处,就是如果需要加新产品,只需要新增工厂实现类就行了,不需要去改原来的代码了。这就易于扩展,解决了简单工厂的缺点。缺点就是代码量也变多了。


抽象工厂模式

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。抽象工厂的源头是一个超级大厂,维护着若干个小厂。就算需求变了这边只需要改具体小厂的制作方法就行了。

image-20200703232011793

1、产品类接口

手机类

1
2
3
4
5
6
public interface PhoneProducton {
void start();
void shutdown();
void call();
void sendMSS();
}

路由器类

1
2
3
4
5
public interface RouterProduction {
void start();
void shutdown();
void openWiFi();
}

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
//小米手机
public class XiaoMiPhone implements PhoneProducton {
@Override
public void start() {
System.out.println("小米手机开启");
}

@Override
public void shutdown() {
System.out.println("小米手机关闭");
}

@Override
public void call() {
System.out.println("小米手机打电话");
}

@Override
public void sendMSS() {
System.out.println("小米手机发短信");
}
}
//小米路由器
public class XiaoMiRouter implements RouterProduction{
@Override
public void start() {
System.out.println("小米路由器开启");
}

@Override
public void shutdown() {
System.out.println("小米路由器关闭");
}

@Override
public void openWiFi() {
System.out.println("小米WiFi开启");
}
}

华为产品

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 HuaWeiPhone implements PhoneProducton {
@Override
public void start() {
System.out.println("华为手机开启");
}

@Override
public void shutdown() {
System.out.println("华为手机关闭");
}

@Override
public void call() {
System.out.println("华为手机打电话");
}

@Override
public void sendMSS() {
System.out.println("华为手机发短信");
}
}
//华为路由器
public class HuaWeiRouter implements RouterProduction{
@Override
public void start() {
System.out.println("华为路由器开启");
}

@Override
public void shutdown() {
System.out.println("华为路由器关闭");
}

@Override
public void openWiFi() {
System.out.println("华为WiFi开启");
}
}

3、抽象工厂,生产抽象产品

1
2
3
4
5
6
7
public interface Factory {
//生产手机
PhoneProducton createPhone();

//生产路由器
RouterProduction createRouter();
}

4、具体品牌工厂实现类,生产各自品牌产品

小米工厂

1
2
3
4
5
6
7
8
9
10
11
public class XiaoMiFactory implements Factory{
@Override
public PhoneProducton createPhone() {
return new XiaoMiPhone();
}

@Override
public RouterProduction createRouter() {
return new XiaoMiRouter();
}
}

华为工厂

1
2
3
4
5
6
7
8
9
10
11
public class HuaWeiFactory implements Factory{
@Override
public PhoneProducton createPhone() {
return new HuaWeiPhone();
}

@Override
public RouterProduction createRouter() {
return new HuaWeiRouter();
}
}

5、消费者测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Consumer {
public static void main(String[] args) {
System.out.println("---------小米产品----------");
PhoneProducton phone = new XiaoMiFactory().createPhone();
RouterProduction router = new XiaoMiFactory().createRouter();
phone.call();
router.openWiFi();
System.out.println("---------华为产品----------");
phone = new HuaWeiFactory().createPhone();
router = new HuaWeiFactory().createRouter();
phone.call();
router.openWiFi();
}
}

大概逻辑图

image-20200704110225339

从代码可以看出,如果产品增加,我们就需要加实现类就行了。但是这样创建产品的过程就非常复杂了,需要增加很多代码。所以抽象工厂是真的工厂,工厂方法模式就相当于一条生产线。


建造者模式

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

何时使用:一些基本部件不会变,而其组合经常变化的时候。

如何解决:将变与不变分离开。

应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的”套餐”。 2、JAVA 中的 StringBuilder。

优点: 1、建造者独立,易扩展。 2、便于控制细节风险。

缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。

使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序

建造者模式将复杂产品的构建过程封装在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。用一个指挥官去指定需要创建哪个对象,指定了创建顺序。把具体的创建过程隐藏了起来。如果需要创建不同种类的对象只需要传入不同的实现类就行了。这就是建造者模式。

但是如果某个产品的内部结构过于复杂,将会导致整个系统变得非常庞大,不利于控制,同时若干个产品之间存在较大的差异,则不适用建造者模式,毕竟这个世界上存在相同点大的两个产品不多,所以它的使用范围有限。

实现

我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。

我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。

然后我们创建一个 Meal 类,带有 ItemArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilderBuilderPatternDemo,我们的演示类使用 MealBuilder 来创建一个 Meal

建造者模式的 UML 图

1、创建一个表示食物条目和食物包装的接口

食物接口 Item.java

1
2
3
4
5
public interface Item {
public String name();
public Packing packing();
public float price();
}

包装接口 Packing.java

1
2
3
public interface Packing {
public String pack();
}

2、创建实现 Packing 接口的实体类

纸盒 Wrapper.java

1
2
3
4
5
6
7
public class Wrapper implements Packing {

@Override
public String pack() {
return "Wrapper";
}
}

瓶子 Bottle.java

1
2
3
4
5
6
7
public class Bottle implements Packing {

@Override
public String pack() {
return "Bottle";
}
}

3、创建实现 Item 接口的抽象类,该类提供了默认的功能

汉堡抽象类 Burger.java

1
2
3
4
5
6
7
8
9
10
public abstract class Burger implements Item {

@Override
public Packing packing() {
return new Wrapper();//注意这里是一个包装类
}

@Override
public abstract float price();
}

冷饮抽象类 ColdDrink.java

1
2
3
4
5
6
7
8
9
10
public abstract class ColdDrink implements Item {

@Override
public Packing packing() {
return new Bottle(); //注意这里是一个包装类
}

@Override
public abstract float price();
}

4、创建扩展了 Burger 和 ColdDrink 的实体类。

素食汉堡 VegBurger.java

1
2
3
4
5
6
7
8
9
10
11
12
public class VegBurger extends Burger {

@Override
public float price() {
return 25.0f;
}

@Override
public String name() {
return "Veg Burger";
}
}

鸡肉汉堡 ChickenBurger.java

1
2
3
4
5
6
7
8
9
10
11
12
public class ChickenBurger extends Burger {

@Override
public float price() {
return 50.5f;
}

@Override
public String name() {
return "Chicken Burger";
}
}

可乐 Coke.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Coke extends ColdDrink {

@Override
public float price() {
return 30.0f;
}

@Override
public String name() {
return "Coke";
}
}

雪碧 Pepsi.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Pepsi extends ColdDrink {

@Override
public float price() {
return 35.0f;
}

@Override
public String name() {
return "Pepsi";
}
}

5、创建一个 Meal 类,带有上面定义的 Item 对象。

套餐 Meal.java

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
import java.util.ArrayList;
import java.util.List;

public class Meal {
private List<Item> items = new ArrayList<Item>();
//套餐增加食物
public void addItem(Item item){
items.add(item);
}
//获取套餐总价格
public float getCost(){
float cost = 0.0f;
for (Item item : items) {
cost += item.price();
}
return cost;
}
//打印套餐信息
public void showItems(){
for (Item item : items) {
System.out.print("Item : "+item.name());
//item.packing()返回的是packing 类对象
System.out.print(", Packing : "+item.packing().pack());
System.out.println(", Price : "+item.price());
}
}
}

6、创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象。

各种套餐组合 MealBuilder.java(指挥官,核心类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MealBuilder {
//套餐一:素食汉堡加可乐
public Meal prepareVegMeal (){
Meal meal = new Meal();
meal.addItem(new VegBurger());
meal.addItem(new Coke());
return meal;
}
//套餐二:鸡肉汉堡加雪碧
public Meal prepareNonVegMeal (){
Meal meal = new Meal();
meal.addItem(new ChickenBurger());
meal.addItem(new Pepsi());
return meal;
}
}

7、BuiderPatternDemo 使用 MealBuider 来演示建造者模式(Builder Pattern)。

顾客点套餐 BuilderPatternDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BuilderPatternDemo {
public static void main(String[] args) {
MealBuilder mealBuilder = new MealBuilder();
//套餐一:素食汉堡加可乐
Meal vegMeal = mealBuilder.prepareVegMeal();
System.out.println("Veg Meal");
vegMeal.showItems();
System.out.println("Total Cost: " +vegMeal.getCost());
//套餐二:鸡肉汉堡加雪碧
Meal nonVegMeal = mealBuilder.prepareNonVegMeal();
System.out.println("\n\nNon-Veg Meal");
nonVegMeal.showItems();
System.out.println("Total Cost: " +nonVegMeal.getCost());
}
}

执行程序,输出结果:

1
2
3
4
5
6
7
8
9
10
Veg Meal
Item : Veg Burger, Packing : Wrapper, Price : 25.0
Item : Coke, Packing : Bottle, Price : 30.0
Total Cost: 55.0


Non-Veg Meal
Item : Chicken Burger, Packing : Wrapper, Price : 50.5
Item : Pepsi, Packing : Bottle, Price : 35.0
Total Cost: 85.5

原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

介绍

意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

主要解决:在运行期建立和删除原型。

优点: 1、性能提高。 2、逃避构造函数的约束。

缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。

使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

什么是浅复制,深复制?

熟悉java的朋友都知道,java自带一个克隆方法(clone)。这是一个native的方法,这个方法不做任何改变的话默认是浅复制的。

  • 浅复制

被复制对象的基本数据类型的值跟原来相同,引用对象类型的还是指向原对象的引用对象,也就是指向地址一样。

  • 深复制

被复制对象的基本数据类型的值跟原来相同,引用对象类型的是指向新对象的,相当于创建了一个新的被引用对象,跟被克隆的原对象不是同一个引用对象。

默认浅复制

数据类 Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person implements Cloneable {

private int age;
private String name;

private Address address;

public Person(int age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}

public Address getAddress() {
return address;
}

@Override
protected Person clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
}

数据类 Address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Address {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Address(String name)
{
this.name=name;
}

}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {

public static void main(String[] args) throws CloneNotSupportedException {

Address address = new Address("北京");
Person p = new Person(19, "李四", address);

//浅复制测试
System.out.println("浅复制测试");

Person p1 = p.clone();

System.out.println(p == p1);

p1.getAddress().setName("深圳");
System.out.println(p);

System.out.println(p1);
}

输出结果

1
2
3
4
5
false

Person{age=19, name='李四', address=Address{name='深圳'}}

Person{age=19, name='李四', address=Address{name='深圳'}}

结论:java默认的clone方法是浅复制,复制后的对象address跟原来的对象指向是一样的。这时大家心里可能会有疑问,如果要使用java默认的clone方法实现深复制。实现深复制的话这边需要改一下原来方法的逻辑,代码如下

使用原生的clone方法实现深复制

数据类 Person 重写clone方法的逻辑

1
2
3
4
5
6
7
@Override
protected Person clone() throws CloneNotSupportedException {
Person clone = (Person) super.clone();
Object o = clone.getAddress().clone();
clone.setAddress((Address) o);
return clone;
}

数据类 Address 需要实现 Cloneable 接口,并且重写Clone()方法

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
public class Address implements Cloneable{
private String name;

public void setName(String name) {
this.name = name;
}

public String getName() {
return name;
}

public Address(String name) {
this.name = name;
}

@Override
public String toString() {
return "Address{" +
"name='" + name + '\'' +
'}';
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {

public static void main(String[] args) throws CloneNotSupportedException {

Address address = new Address("深圳");
Person p = new Person(19, "李四", address);

//深复制实现clone的方式
System.out.println("深复制测试");
Person p2 = p.clone();

System.out.println(p == p2);

p2.getAddress().setName("北京");
System.out.println(p);
System.out.println(p2);
}
}

根据我们测试用例在来跑一次

输出结果

1
2
3
false 
Person{age=19, name='李四', address=Address{name='深圳'}}
Person{age=19, name='李四', address=Address{name='北京'}}

深复制总结:

① 如果有一个非原生成员,如自定义对象的成员,那么就需要深复制: 该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为protected可继承。 同时,修改被复制类的clone()方法,增加成员的克隆逻辑。

② 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。与对象成员不同,继承关系中的clone不需要被复制类的clone()做修改。

一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。

前面的实例还可以接受,如果有N个对象成员,有M层继承关系,就会很麻烦。

使用序列化的方式实现深复制

这个方式可以理解为,把对象转化成二进制流的形式,然后再把流序列化成对象。 具体实现方法

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
/** 
* person类和address类都要实现serializable接口
* @return 使用读二进制形式流的方式实现深克隆
* @throws CloneNotSupportedException
*/
public Person deepCopy() {

/* 写入当前对象的二进制流 */
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 (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
bos.close();
oos.close();
bis.close();
ois.close();
}
/* 读出二进制流产生的新对象 */
return null;
}

测试

1
2
3
4
5
6
7
8
9
//深复制实现流的方式
System.out.println("深复制测试二进制流");
Person p3 = p.deepCopy();

System.out.println(p == p3);

p3.getAddress().setName("上海");
System.out.println(p);
System.out.println(p3);

输出结果

1
2
3
false 
Person{age=19, name='李四', address=Address{name='深圳'}}
Person{age=19, name='李四', address=Address{name='上海'}}

序列化总结: 这种方式被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。这种是最常用,而且比较简单的方法。

总结

所谓原型模式,就是通过复制对象来创建对象。与通过使用构造函数来比,就是通过复制的对象带有原来对象的一些状态。尤其是在一些场景对象只存在细微差别。这时就可以使用原型模式去创建对象。