0%

设计模式之Prototype模式

介绍

  • 意图: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
  • 何时使用:
    • 当一个系统应该独立于它的产品创建,构成和表示时。
    • 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
    • 为了避免创建一个与产品类层次平行的工厂类层次时。
    • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
  • 解决方法:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
    • 实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。(.NET是个什么东西感兴趣的可以自行查阅)
    • 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些”易变类”拥有稳定的接口。

实现


Product.java

1
2
3
4
5
6
package com.edu.tju.GOF.Prototype.framework;

public interface Product extends Cloneable {
public abstract void use(String word);
public abstract Product createClone();
}

Manager.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.edu.tju.GOF.Prototype.framework.*;

import java.util.HashMap;

public class Manager {
HashMap hashmap = new HashMap();
public void register(String key,Product p){
hashmap.put(key, p);
}
public Product create(String key){
Product p=(Product)hashmap.get(key);
return p.createClone();
}
}

MessageBox.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
28
29
30
31
32
33
package com.edu.tju.GOF.Prototype;
import com.edu.tju.GOF.Prototype.framework.*;

public class MessageBox implements Product {
private char decochar;

public MessageBox(char decochar) {
this.decochar = decochar;
}

public void use(String s) {
int length = s.getBytes().length;
for (int i = 0; i < length + 4; i++) {
System.out.print(decochar);
}
System.out.println("");
System.out.println(decochar + " " + s + " " + decochar);
for (int i = 0; i < length + 4; i++) {
System.out.print(decochar);
}
System.out.println("");
}

public Product createClone() {
Product p = null;
try {
p = (Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}

Underline.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
28
29
30
31
package com.edu.tju.GOF.Prototype;

import com.edu.tju.GOF.Prototype.framework.*;

public class Underline implements Product {
private char ulchar;

public Underline(char ulchar) {
this.ulchar = ulchar;
}

public void use(String s) {
int length = s.getBytes().length;
System.out.println("\"" + s + "\"");
System.out.print(" ");
for (int i = 0; i < length; i++) {
System.out.print(ulchar);
}
System.out.println("");
}

public Product createClone() {
Product p = null;
try {
p = (Product) clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}

Main.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
28
29
30
package com.edu.tju.GOF.Prototype;

import com.edu.tju.GOF.Prototype.framework.Manager;
import com.edu.tju.GOF.Prototype.framework.Product;

public class Main {
public static void main(String[] args) {
// 准备
Manager manager = new Manager();
Underline uline = new Underline('~');
MessageBox mbox = new MessageBox('*');
MessageBox sbox = new MessageBox('/');
manager.register("strong message", uline);
manager.register("warning box", mbox);
manager.register("slash box", sbox);

// 生成
Product p1 = manager.create("strong message");
p1.use("Hello,rlj.");
Product p2 = manager.create("warning box");
p2.use("Hello,jason.");
Product p3 = manager.create("slash box");
p3.use("Hello,girls.");
Product p4 = manager.create("slash box");
p3.use("Hello,girls.");
System.out.println(p1.getClass() == p2.getClass());//false
System.out.println(p3.getClass() == p4.getClass());//true

}
}

登场角色

原型模式中有三个登场角色:

  1. 原型角色(Prototype)
    Product角色负责定义用于复制现有实例来生成新的实例的方法.在示例程序中,由Product接口扮演此角色.
  2. 具体原型角色(ConcretePrototype)
    ConcretePrototype 角色负责实现复制现有实例并生成新实例的方法.在示例程序中,由MessageBox类和Underline类扮演此角色.
  3. 使用者角色(Client):维护一个注册表,并提供一个找出正确实例原型的方法。最后,提供一个获取新实例的方法,用来委托复制实例的方法生成新实例。
    在实例中Manager类扮演此角色.

拓展阅读

  1. 在Java中,我们可以使用new关键字指定类名来生成类的实例.像这样使用new来生成实例时,是必须指定类名的.但是在开发过程中,有时候也会有”在不指定类名的前提下生成实例”的需求.例如一下的情况,我们就不能根据类来生成实例,而要根据现有的实例来生成实例

    • 对象种类繁多,无法将它们整合在一个类中时
    • 难以根据类生成实例时
      生成实例的过程过于负责,很难根据类来生成实例.
    • 想解耦框架与生成的实例时
  2. python 中的 原型模式

    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
    import copy
    # 用copy包实现深拷贝(copy.deepcopy())和浅拷贝(copy.copy())
    from abc import abstractmethod,ABCMeta

    # 创建一接口
    class Shape(metaclass=ABCMeta):
    _id =""
    in_type = ""

    @abstractmethod
    def draw(self):
    pass

    def getType(self):
    return self.in_type

    def getID(self):
    return self._id

    def setID(self,in_id):
    self._id = in_id

    def clone(self):
    #深拷贝
    myclone = copy.deepcopy(self)
    return myclone

    # 创建实体类
    class Rectangle(Shape):
    def __init__(self):
    self.in_type = "Rectangel"

    def draw(self):
    print("Inside Rectangle.draw() method.")

    class Square(Shape):
    def __init__(self):
    self.in_type = "Square"

    def draw(self):
    print("Inside Square.draw() method.")

    class Circle(Shape):
    def __init__(self):
    self.in_type = "Circle"

    def draw(self):
    print("Inside Circle.draw() method.")

    # 获取数据实体类
    class ShapeCache():
    #Python 无静态变量,用开放类变量
    shapeMap = {}

    def getShape(self,shapeID):
    cachedShape = self.shapeMap[shapeID]
    return cachedShape.clone()

    # 静态方法
    @staticmethod
    def loadCache():
    circle1 = Circle()
    circle1.setID("1")
    ShapeCache.shapeMap[circle1.getID()] = circle1

    square1 = Square()
    square1.setID("2")
    ShapeCache.shapeMap[square1.getID()] = square1

    rectangle1 = Rectangle()
    rectangle1.setID("3")
    ShapeCache.shapeMap[rectangle1.getID()] = rectangle1

    # 调用输出
    if __name__ == '__main__':
    ShapeCache.loadCache()
    myShape = ShapeCache()

    cloneShape1 = myShape.getShape("1")
    print("Shape : %s" %cloneShape1.getType())
    cloneShape1.draw()

    cloneShape2 = myShape.getShape("2")
    print("Shape : %s" % cloneShape2.getType())
    cloneShape2.draw()

    cloneShape3 = myShape.getShape("3")
    print("Shape : %s" % cloneShape3.getType())
    cloneShape3.draw()
  3. clone方法和java.lang.Cloneable接口

    Java语言为我们准备了用于复制实例的clone方法.请注意,要想调用clone方法,被复制对象的类必须实现java.lang.Cloneable接口,不论是被复制对象的类实现了Cloneable接口还是其某个父类实现Cloneable接口,亦或是被复制对象的类实现了Cloneable接口的子接口都可以.

    实现了Cloneable接口的类的实例可以调用clone 进行复制,clone方法的返回值是复制出的新的实例(clone方法内部所进行的处理是分配与要复制的实例同样大小的空间,接着将要复制的实例字段的值复制到所分配的内存空间中去).

    如果没有实现Cloneable接口的类的实例调用了clone方法,则会在运行时抛出CloneNotSupportedException异常.

  • 那么clone方法是在哪里定义的呢?
    clone方法定义在java.lang.Object中.因为Object类是所有Java类的父类,因此所有的Java类都继承了clone方法.

  • 我们需要实现Cloneable的哪些方法呢?
    提到Cloneable接口,很容易让人误以为Cloneable接口中声明了clone方法,其实这是错误的.在Cloneable接口中并没有声明任何方法.它知识被用来标记”可以使用clone方法进行复制”的.这样的接口被称为标记接口.

  • clone方法进行的是浅拷贝

    深拷贝和浅拷贝

    个人理解就是,比如说我有一双aj11康扣,有一天刷完鞋丢了.
    可是我很喜欢这双鞋,所以我又买了一双(深拷贝),并且在这双鞋上签上了名字(修改对象参数,不会影响原来的康扣,重新分配空间).
    但是有一天,我突然又找到了我丢的那双,上面一定是没有写着名字的(勉强看作浅拷贝吧.鞋还是原来的鞋)
    传送🚪

    思考题

  1. 除了Cloneable,java中还有哪些常见的标记接口呢?
Thank you for your support