原型模式

原型模式

一、定义

摘自百度百科: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

点击查看源码


二、实现方式

原型模式要求对象拥有一个可以 克隆自己 的方法,如此一来,当通过原型实例创建新对象时,就可以 不用关心实例本身 ,也无需通过new对象来实现。

深克隆与浅克隆的区别

深克隆和浅克隆都是指对象复制的方式,但它们之间有一些区别。

深克隆 创建一个新对象,并且将原始对象及其所有嵌套对象的数据复制到新对象中 。这意味着,新对象的所有属性都是不同的对象实例 。这样,如果你改变原始对象中的任何属性,新对象的对应属性不会受到影响,并且反之亦然。深克隆通常 需要更多的时间和资源 来复制所有属性和嵌套对象。

浅克隆只会 复制原始对象中的所有属性 ,但是所有 嵌套的对象都将是原始对象中相应属性的引用 。这意味着,新对象的属性和原始对象中的属性将共享对象实例。因此,如果你 在新对象中更改一个嵌套对象的属性,则原始对象中相应的属性也会受到影响 ,反之亦然。相对于深克隆,浅克隆需要的时间和资源更少,但可能会导致意外的行为,因为对象引用可能会被共享并且相互影响。

原型模式具体有两种实现方式,一种是 简单形式 ,另一种是 登记形式 。我们就这两种形式来探讨一下原型模式的实现方式。

1.简单形式

(1)角色分类

  • 客户(Client)

    提出创建对象的请求的角色

  • 抽象原型(Prototype)

    规定具体原型类要实现的方法

  • 具体原型(Concrete Prototype)

    真正实现创建对象的角色

(2)UML图

未命名文件 (4)

(3)具体实现

抽象原型(Prototype)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class Prototype {
private String name;

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

public String getName(){
return name;
}

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

/**
* 克隆方法
* @return Prototype
*/
public abstract Prototype clone();
}

具体原型A(Concrete Prototype A)

1
2
3
4
5
6
7
8
9
public class ConcretePrototypeA extends Prototype {
public ConcretePrototypeA (String name) {
super(name);
}

public Prototype clone() {
return new ConcretePrototype(this.name);
}
}

具体原型B(Concrete Prototype B)

1
2
3
4
5
6
7
8
9
public class ConcretePrototypeB extends Prototype {
public ConcretePrototypeB (String name) {
super(name);
}

public Prototype clone() {
return new ConcretePrototype(this.name);
}
}

客户(Client)

1
2
3
4
5
6
7
8
9
10
11
12
public static void main() {
System.out.println("prototype1");
ConcretePrototype1 prototype1 = new ConcretePrototype1("prototype1");
Prototype clone1 = prototype1.clone();
System.out.println("prototype1:" + prototype1);
System.out.println("clone:" + clone1);
System.out.println("prototype2");
ConcretePrototype2 prototype2 = new ConcretePrototype2("prototype2");
Prototype clone2 = prototype2.clone();
System.out.println("prototype2:" + prototype2);
System.out.println("clone:" + clone2);
}

运行结果

image.png

2.登记形式

(1)角色分类

  • 客户(Client)

    提出创建对象的请求的角色

  • 抽象原型角色(Prototype)

    规定具体原型类要实现的方法

  • 具体原型角色(Concrete Prototype)

    真正实现创建对象的角色

  • 原型管理器(Prototype Manager)

    提供原型对象的创建以及其他管理的方法

(2)UML图

未命名文件 (1).png

(3)具体实现

抽象原型对象(Prototype)

1
2
3
4
5
public interface Prototype {
public Prototype clone();
public String getName();
public void setName(String name);
}

具体原型A(Concrete Prototype 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
public class ConcretePrototypeA implements Prototype {
private String name;

@Override
public String getName() {
return this.name;
}

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

@Override
public Prototype clone() {
Prototype prototype = new ConcretePrototypeA();
prototype.setName(this.name);
return prototype;
}

@Override
public String toString() {
return "ConcretePrototypeA [name=" + name + "]";
}
}

具体原型B(Concrete Prototype B)

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

@Override
public String getName() {
return this.name;
}

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

@Override
public Prototype clone() {
Prototype prototype = new ConcretePrototypeB();
prototype.setName(this.name);
return prototype;
}

@Override
public String toString() {
return "ConcretePrototypeB [name=" + name + "]";
}
}

原型管理器(Prototype Manager)

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
public class PrototypeManager {
/**
* 记录原型的编号同原型实例的对象关系
*/
private static Map<String, Prototype> map = new HashMap<>();

/**
* 私有化构造方法,避免从外部创建实例
*/
private PrototypeManager(){};

/**
* 向原型管理器里面添加或者修改原型实例
*
* @param prototypeId 原型编号
* @param prototype 原型实例
*/
public static void setPrototype(String prototypeId, Prototype prototype) {
map.put(prototypeId, prototype);
}

/**
* 根据原型编号删除原型实例
*
* @param prototypeId 原型编号
*/
public static void removePrototype(String prototypeId) {
map.remove(prototypeId);
}

/**
* 根据原型编号获取原型实例
*
* @param prototypeId 原型编号
* @return 原型实例对象
* @throws RuntimeException
*/
public static Prototype getPrototype(String prototypeId) throws RuntimeException {
Prototype prototype = map.get(prototypeId);
if(Objects.isNull(prototype)){
throw new RuntimeException("原型" + prototypeId + "不存在");
}
return prototype;
}
}

客户(Client)

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
public class Client {
public static void main(String[] args){
try {
// 创建实例
Prototype prototypeA = new ConcretePrototypeA();
// 注册实例
PrototypeManager.setPrototype("prototypeA",prototypeA);
// 克隆
Prototype prototypeC = PrototypeManager.getPrototype("prototypeA").clone();
prototypeC.setName("张三");
System.out.println("第一个实例副本:" + prototypeC);

// 创建实例
Prototype prototypeB = new ConcretePrototypeB();
// 注册实例
PrototypeManager.setPrototype("prototypeB",prototypeB);
// 克隆
Prototype prototypeD = PrototypeManager.getPrototype("prototypeB").clone();
prototypeD.setName("李四");
System.out.println("第二个实例副本:" + prototypeD);

// 销毁第一个实例
PrototypeManager.removePrototype("prototypeA");
// 再次克隆第一个实例
Prototype prototypeE = PrototypeManager.getPrototype("prototypeA").clone();
prototypeE.setName("王五");
System.out.println("第一个实例副本:" + prototypeE);
} catch (Exception e) {
e.printStackTrace();
}
}
}

运行结果

image.png


三、应用场景

以下部分内容摘自菜鸟教程

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

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

何时使用:

  1. 当一个系统应该独立于它的产品创建,构成和表示时。
  2. 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
  3. 为了避免创建一个与产品类层次平行的工厂类层次时。
  4. 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。

关键代码:

  1. 实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。
  2. 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些”易变类”拥有稳定的接口。

应用实例:

  1. 细胞分裂。
  2. JAVA 中的 Object clone() 方法。

优点:

  1. 性能提高。
  2. 逃避构造函数的约束。

缺点:

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

使用场景:

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

需要注意的是,原型模式的使用场景应遵循以下几个条件:

  • 对象的创建成本较高,而复制对象的成本较低
  • 对象的初始化过程相对稳定,不会经常发生变化。
  • 对象的属性和状态可以通过浅克隆(复制引用)或深克隆(复制值)来进行复制。

总而言之,原型模式在需要高效创建对象副本、保护对象状态、动态配置对象等场景下是非常有用的。它提供了一种灵活的方式来创建和复制对象,避免了传统实例化的开销和复杂性。


四、优缺点

优点

  • 性能提升(相比new一个对象)。当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
  • 在克隆对象时无需与原始对象所属的具体类相耦合。

缺点

  • 必须实现Cloneable接口或者实现序列化Serializable接口,每一个类都必须要配备一个克隆方法,配备克隆方法需要对类的功能进行通盘考虑
  • 在实现深拷贝时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深拷贝,每一次对象的类对必须支持深拷贝,实现起来可能比较麻烦。