[设计模式/Java/序列化] 设计模式之原型模式【13】
0 序
本篇主要参考【参考文献】的第1篇。
1 概述:原型模式 := Prototype Pattern
模式定义
- 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
- 在软件系统中,有时候需要多次创建某一类型的对象,为了简化创建过程,可以只创建一个对象,然后再通过克隆的方式复制出多个相同的对象,这就是原型模式的设计思想。
- 原型模式的基本工作原理
通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象来复制原型自己来实现创建过程。
- 举例说明
《西游记》中孙悟空拔毛变小猴的故事几乎人人皆知,孙悟空可以用猴毛根据自己的形象,复制出很多跟自己长得一模一样的身外身来。

孙悟空这种复制出多个身外身的方式在面向对象设计领域里称为原型(Prototype)模式。
在面向对象系统中,使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象。
- 在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建。
原型模式通过给出一个原型对象来指明所要创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象,这就是原型模式的意图所在。
模式的组成
- Prototype(抽象原型类)
抽象原型类是定义具有克隆自己的方法的接口,是所有具体原型类的公共父类,可以是抽象类,也可以是接口。
- ConcretePrototype(具体原型类)
具体原型类实现具体的克隆方法,在克隆方法中返回自己的一个克隆对象。
- Client(客户类)
客户类让一个原型克隆自身,从而创建一个新的对象。在客户类中只需要直接实例化或通过工厂方法等方式创建一个对象,再通过调用该对象的克隆方法复制得到多个相同的对象。
细分模式: 深拷贝模式 vs 浅拷贝模式
概念对比
- 
原型模式的克隆分为浅克隆和深克隆 
- 
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。 

- 深克隆:创建一个新对象,属性中引用的其他对象(非基本类型的属性)也会被克隆,不再指向原有对象地址。

下面通过两个分别实现浅克隆和深克隆的实例来进一步学习并理解原型模式。
案例1:原型模式实例之邮件复制(浅克隆)

1. 抽象原型类Object(无需创建)
Object作为抽象原型类,在Java语言中,所有的类都是Object的子类,在Object中提供了克隆方法clone(),用于创建一个原型对象,其clone()方法具体实现由JVM完成,用户在使用时无须关心。

2. 附件类 Attachment
为了更好地说明浅克隆和深克隆的区别,在本案例中引入了附件类
Attachment、邮件类
package prototype;
public class Attachment {
    public void download(){
        System.out.println("下载附件");
    }
}
3. 具体原型类Email(邮件类)
- Email类是具体原型类,也是- Object类的子类。
在Java语言中,只有实现了
Cloneable接口的类才能够使用clone()方法来进行复制。
因此,Cloneable接口。
在Object的clone()方法,通过直接或者间接调用Object的clone()方法返回一个克隆后的原型对象。
在attachment,其类型为Attachment。
package prototype;
//@author mengzhichao | @create 2021-11-10-22:18
public class Email implements Cloneable {
    private Attachment attachment=null;
    public Email(){
        this.attachment=new Attachment();
    }
    @Override
    public Object clone(){
        Email clone =null;
        try {
            clone=(Email) super.clone();
        }catch (CloneNotSupportedException e){
            System.out.println("Clone failure!");
        }
        return clone;
    }
    public Attachment getAttachment(){
        return this.attachment;
    }
    public void display(){
        System.out.println("查看邮件");
    }
}
4. 客户端测试类 Client
- 在 Client客户端测试类中,比较原型对象和复制对象是否一致,并比较其成员对象attachment的引用是否一致。
package prototype;
public class Client {
    public static void main(String[] args) {
        Email email,copyEmail;
        email=new Email();
        copyEmail= (Email) email.clone();
        System.out.println("email == copyEmail?");
        System.out.println(email == copyEmail);
        System.out.println("email.getAttachment() == copyEmail.getAttachment()?");
        System.out.println(email.getAttachment() == copyEmail.getAttachment());
    }
}
out
email == copyEmail?
false
email.getAttachment() = copyEmail.getAttachment()?
true
- 通过结果可以看出,表达式(email==copyEmail)结果为false
即: 通过复制得到的对象与原型对象的引用不一致。也就是说明,在内存中存在两个完全不同的对象,一个是原型对象,一个是克隆生成的对象。
- 但是表达式(email.getAttachment( ) == copyEmail.getAttachment())结果为true,两个对象的成员对象是同一个,说明虽然对象本身复制了一份,但其成员对象在内存中没有复制,原型对象和克隆对象维持了对相同的成员对象的引用。
案例2:原型模式实例之邮件复制(深克隆)
- 使用深克隆实现邮件复制
即 复制邮件的同时复制附件。

1. 附件类 Attachment
- 作为Email类的成员对象,在深克隆中,Attachment类型的对象也将被写入流中
因此,
Attachment类也需要实现Serializable接口。
package prototype2;
import java.io.Serializable;
public class Attachment implements Serializable {
    public void download(){
        System.out.println("下载附件");
    }
}
2. 具体原型类Email(邮件类)
- Email作为具体原型类
由于实现的是深克隆,无须使用
Object的clone()方法
因此,无须实现Cloneable接口;
可以通过序列化的方式实现深克隆(代码中粗体部分)
由于要将Serializable接口。
package prototype2;
import java.io.*;
public class Email implements Serializable {
    private Attachment attachment = null;
    public Email(){
        this.attachment = new Attachment();
    }
    public Object deepClone() throws IOException, ClassNotFoundException, OptionalDataException{
        //将对象写入流中
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bao);
        oos.writeObject(this);
        //将对象从流中取出
        ByteArrayInputStream bis =new ByteArrayInputStream(bao.toByteArray());
        ObjectInputStream ois =new ObjectInputStream(bis);
        return (ois.readObject());
    }
    public Attachment getAttachment(){
        return this.attachment;
    }
    public void display(){
        System.out.println("查看邮件");
    }
}
3. 客户端测试类 Client
- 在Client客户端测试类中,我们仍然比较深克隆后原型对象和拷贝对象是否一致,并比较其成员对象attachment的引用是否一致。
package prototype2;
public class Client {
    public static void main(String[] args) {
        Email email,copyEmail = null;
        email = new Email();
        try {
            copyEmail = (Email) email.deepClone();
        } catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("email==copyEmail?");
        System.out.println(email==copyEmail);
        System.out.println("email.getAttachment()==copyEmail.getAttachment()?");
        System.out.println(email.getAttachment()==copyEmail.getAttachment());
    }
}
out
emial==copyEmail?
false
emial.getAttachment()==copyEmail.getAttachment()?
false
- 通过结果可以看出:
- 表达式(
email==copyEmail)结果为false即: 通过复制得到的对象与原型对象的引用不一致,表达式(
email.getAttachment()==copyEmail.getAttachment())结果也为false
- 原型对象与克隆对象对成员对象的引用不相同,说明其成员对象也复制了一份。
模式特点
优点
- 原型模式最大的优点:
- 可以快速创建很多相同或相似的对象,简化对象的创建过程,还可以保存对象的一些中间状态。
缺点
- 需要为每一个类配备一个克隆方法
因此,对已有类进行改造比较麻烦。需要修改其源代码,并且在实现深克隆时需要编写较为复杂的代码。
适用场景
- 适用于:创建新对象的成本较大,新的对象可以通过原型模式对已有对象进行复制来获得。
系统要保存对象的状态,而对象的状态变化很小,需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
延申:带原型管理器的原型模式
- 原型模式的一种改进形式是带原型管理器的原型模式。
原型管理器(
Prototype Manager)角色创建具体原型类的对象,并记录每一个被创建的对象。
原型管理器的作用与工厂相似,其中定义了一个集合用于存储原型对象
如果需要某个对象的一个克隆,可以通过复制集合中对应的原型对象来获得。
在原型管理器中针对抽象原型类进行编程,以便扩展。

案例:颜色原型管理器
下边,使用代码模拟演示一个颜色原型管理器的实现过程
1. 抽象原型类 MyColor
package prototypemanager;
public interface MyColor extends Cloneable {
    public Object clone();
    public void display();
}
2. 具体原型类 Red
package prototypemanager;
public class Red implements MyColor {
    @Override
    public Object clone() {
        Red r=null;
        try {
            r = (Red) super.clone();
        }catch (Exception e){
        }
        return r;
    }
    @Override
    public void display() {
        System.out.println("This is Red");
    }
}
3. 具体原型类 Blue
package prototypemanager;
public class Blue implements MyColor {
    @Override
    public Object clone() {
        Blue b = null;
        try {
            b = (Blue) super.clone();
        } catch (Exception e){
        }
        return b;
    }
    @Override
    public void display() {
        System.out.println("This is Blue");
    }
}
4. 原型管理器类 PrototypeManager
package prototypemanager;
import java.util.Hashtable;
public class PrototypeManager {
    private Hashtable ht = new Hashtable();
    public PrototypeManager() {
        ht.put("red", new Red());
        ht.put("blue", new Blue());
    }
    public void addColor(String key, MyColor obj){
        ht.put(key,obj);
    }
    public MyColor getColor(String key){
        return (MyColor) ((MyColor)ht.get(key)).clone();
    }
}
5. Client
package prototypemanager;
public class Client {
    public static void main(String[] args) {
        PrototypeManager pm = new PrototypeManager();
        MyColor obj1 = pm.getColor("red");
        obj1.display();
        MyColor obj2 = pm.getColor("red");
        obj2.display();
        System.out.println(obj1 == obj2);
    }
}
out
This is Red
This is Red
false
- 在PrototypeManager中定义了一个Hashtable类型的集合
- 使用“键值对”来存储原型对象
- 客户端可以通过
Key来获取对应原型对象的克隆对象。
PrototypeManager类提供了工厂方法,用于返回一个克隆对象。
案例实践
案例:FastJson对调用API后的 JSON 响应内容反序列化为 Java 对象 | JSONObject/JSONArray 转 Java 对象 / Java 对象List
推荐章节:
- 案例: FastJson对调用API后的 JSON 响应内容反序列化为 Java 对象 | JSONObject/JSONArray 转 Java 对象 / Java 对象List
Y 推荐文献
- [Java EE]辨析: POJO(PO / DTO / VO) | BO/DO | DAO - 博客园/千千寰宇
- [Java] Spring BeanUtils:Java数据对象转换工具、对象属性操纵助手 - 博客园/千千寰宇
原型设计模式:浅拷贝 vs 深拷贝
Spring的BeanUtils是浅拷贝
X 参考文献
 
    本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号