每日设计模式之原型模式

Java版

原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。

原型模式的工作原理:将一个原型对象传给那个要进行创建的对象,这个要进行创建的对象通过请求原型对象拷贝自己来实现创建过程。

原型模式在Java中实现起来非常简单,主要分为浅克隆和深度克隆。

浅克隆

浅克隆非常简单,待拷贝的对象只需要实现Cloneable接口即可。

在Java中,数据类型分为值类型(基本数据类型)和引用类型,而浅克隆和深度克隆的主要区别在于对于引用类型的克隆。

浅克隆中,如果我们的原型对象的成员变量是值类型,将会被复制一份给克隆对象,如果原型对象的成员是引用类型的话,择会将引用对象的地址复制一份给克隆对象,说白了就是克隆对象的引用类型变量和原型对象的引用类型变量指向的是同一个相同的内存地址。

下面我们给出一个浅克隆的代码实现例子:

①WorkExperience类(工作经历类)

package cn.lframe.designmode.prototypepattern.secondmethodshallowclone;

import lombok.Data;

/**
 * @author Lframe
 * @create2018 -05 -10 -9:27
 */
@Data
public class WorkExperience {

    private String enterprise;
    private Integer duration;

    public WorkExperience(String enterprise, Integer duration) {
        this.enterprise = enterprise;
        this.duration = duration;
    }


}

②Person类:

package cn.lframe.designmode.prototypepattern.secondmethodshallowclone;

import lombok.Data;

/**
 * @author Lframe
 * @create2018 -05 -10 -9:26
 */
@Data
public class Person implements Cloneable {

    private String name;

    private Integer age;

    private WorkExperience workExperience;

    public Person(String name, Integer age, WorkExperience workExperience) {
        this.name = name;
        this.age = age;
        this.workExperience = workExperience;
    }

    public Person() {
    }

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

③测试类:

package cn.lframe.designmode.prototypepattern.secondmethodshallowclone;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Lframe
 * @create2018 -05 -10 -9:30
 */
@Slf4j
public class Test {

    public static void main(String[] args) {
        WorkExperience workExperience = new WorkExperience("华为", 5);
        Person person = new Person("lframe", 28, workExperience);
        Person clonePerson = null;
        try {
            clonePerson = (Person) person.clone();
        } catch (CloneNotSupportedException e) {
            log.error("【不支持克隆】");
        }
        log.info("Person与克隆对象的地址是否一致: {}", person == clonePerson);
        log.info("Person的workExperience与克隆对象的workExperience地址是否一致:{}",
                person.getWorkExperience() == clonePerson.getWorkExperience());

    }
}

测试结果:

cn.lframe.designmode.prototypepattern.secondmethodshallowclone.Test
11:59:24.036 [main] INFO cn.lframe.designmode.prototypepattern.secondmethodshallowclone.Test - Person与克隆对象的地址是否一致: false
11:59:24.051 [main] INFO cn.lframe.designmode.prototypepattern.secondmethodshallowclone.Test - Person的workExperience与克隆对象的workExperience地址是否一致:true

Process finished with exit code 0

下面给出深度克隆的实现

在深度克隆中,无论原型对象的成员变量是值类型还是引用类型,都将会被复制一份给克隆对象。在深度克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

在Java中,深度克隆是通过序列化方式来实现。序列化就是将对象写入到流中,写入流的对象是原有对象的一个克隆,而原来对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将一个对象写入到流中,然后再从流中将其读取出来,即可实现深度克隆。
需要序列化的对象必须实现Serializable接口。在深度克隆中,无论是原型对象还是原型对象中的引用类型变量,他们都必须实现序列化接口Serializable。

下面给出一个例子:

*①WorkExperience类(工作经历类,它作为Person类的一个实例变量)*

package cn.lframe.designmode.prototypepattern.thirdlymethoddeepclone;

import lombok.Data;

import java.io.Serializable;

/**
 * @author Lframe
 * @create2018 -05 -10 -9:58
 */
@Data
public class WorkExperience implements Serializable {

    private String enterprise;
    private Integer duration;

    public WorkExperience(String enterprise, Integer duration) {
        this.enterprise = enterprise;
        this.duration = duration;
    }

}

②下面给出Person类

package cn.lframe.designmode.prototypepattern.thirdlymethoddeepclone;

import lombok.Data;

import java.io.*;

/**
 * @author Lframe
 * @create2018 -05 -10 -9:57
 */
@Data
public class Person implements Serializable {

    private String name;

    private Integer age;

    private WorkExperience workExperience;

    public Person(String name, Integer age, WorkExperience workExperience) {
        this.name = name;
        this.age = age;
        this.workExperience = workExperience;
    }

    public Person deepClone() throws IOException, ClassNotFoundException {
//        将对象写入流中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(this);

//        将对象从流中读出
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return (Person) objectInputStream.readObject();

    }

}

③测试类

package cn.lframe.designmode.prototypepattern.thirdlymethoddeepclone;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

/**
 * @author Lframe
 * @create2018 -05 -10 -9:58
 */
@Slf4j
public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        WorkExperience workExperience = new WorkExperience("华为", 2);
        Person peroson = new Person("lframe", 28, workExperience);
        Person clonePerson = null;
        try {
            clonePerson = peroson.deepClone();
        } catch (IOException e) {
            log.error("【IOE异常】");
            throw new IOException();
        } catch (ClassNotFoundException e) {
            log.error("【没有发现类异常】");
            throw new ClassNotFoundException();
        }

        log.info("Person与克隆对象的地址是否一致: {}", peroson == clonePerson);
        log.info("Person的workExperience与克隆对象的workExperience地址是否一致:{}",
                peroson.getWorkExperience() == clonePerson.getWorkExperience());


    }

}

④测试结果:

13:13:47.774 [main] INFO cn.lframe.designmode.prototypepattern.thirdlymethoddeepclone.Test - Person与克隆对象的地址是否一致: false
13:13:47.791 [main] INFO cn.lframe.designmode.prototypepattern.thirdlymethoddeepclone.Test - Person的workExperience与克隆对象的workExperience地址是否一致:false

原型管理器的接入与实现

原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展。

下面给出代码实现:
①OfficialDocument类(抽象的办公文档接口)

package cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement;

/**
 * 抽象的公文接口
 */
public interface OfficialDocument extends Cloneable{

    OfficialDocument clone();

    void display();
}

②下面给出它的两个实现类,分别是FAR,SRS

FAR

package cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Lframe
 * @create2018 -05 -10 -10:41
 */
@Slf4j
public class FAR implements OfficialDocument {

    @Override
    public OfficialDocument clone() {
        OfficialDocument far = null;
        try {
            far = (OfficialDocument) super.clone();
        } catch (CloneNotSupportedException e) {
            log.error("【FAR不支持复制】");
        }
        return far;
    }

    @Override
    public void display() {
        log.info("《可行性分析报告》");
    }

}

SRS

package cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Lframe
 * @create2018 -05 -10 -10:45
 */
@Slf4j
public class SRS implements OfficialDocument {

    @Override
    public OfficialDocument clone() {
        OfficialDocument srs = null;
        try {
            srs = (OfficialDocument) super.clone();
        } catch (CloneNotSupportedException e) {
            log.error("【FAR不支持复制】");
        }
        return srs;
    }

    @Override
    public void display() {
        log.info("【软件规格说明书】");
    }
}

③原型管理器PrototypeManagement类

package cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement;

import lombok.extern.slf4j.Slf4j;

import java.util.Hashtable;

/**
 * 使用饿汉单例模式
 * 原型管理器
 *
 * @author Lframe
 * @create2018 -05 -10 -10:48
 */
@Slf4j
public class PrototypeManagement {

    private Hashtable<String, OfficialDocument> hashtable = new Hashtable<>();

    private static PrototypeManagement prototypeManagement = new PrototypeManagement();

    private PrototypeManagement() {
        hashtable.put("far", new FAR());
        hashtable.put("srs", new SRS());
    }

    public void addOfficialDocument(String key, OfficialDocument officialDocument)throws Exception {
        try {
            hashtable.put(key, officialDocument);

        }catch (Exception e){
            log.error("【内部系统错误】");
            throw new Exception();
        }

    }

    /**
     * 这里使用的是浅克隆
     * @return
     */
    public OfficialDocument getOfficialDocument(String key){
        OfficialDocument officialDocument = null;
        try {
            officialDocument=hashtable.get(key).clone();
        }catch (NullPointerException e){
            log.error("【目前不支持该种文件类型】");
            throw new NullPointerException();
        }catch (ClassCastException e){
            log.error("【圆形管理器系统错误】");
            throw new ClassCastException();
        }
        return officialDocument;

    }

    public static PrototypeManagement getPrototypeManagement() {
        return prototypeManagement;
    }
}

④测试类

package cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Lframe
 * @create2018 -05 -10 -10:57
 */
@Slf4j
public class Test {

    public static void main(String[] args) {

        /**
         * 使用了饿汉模式。
         * 对外不提供构造器和set()方法,只提供get()方法。
         */
        PrototypeManagement prototypeManagement = PrototypeManagement.getPrototypeManagement();

        OfficialDocument firstFAR ;
        OfficialDocument secondFAR ;
        OfficialDocument firstSRS ;
        OfficialDocument secondSRS ;

        firstFAR = prototypeManagement.getOfficialDocument("far");
        firstFAR.display();

        secondFAR = prototypeManagement.getOfficialDocument("far");
        secondFAR.display();

        log.info("【在原型管理器克隆的FAR地址是否相同:】 {}",firstFAR==secondFAR);
        log.info("【在原型管理器克隆的FAR内容是否相同:】 {}",firstFAR.equals(secondFAR));

        firstSRS = prototypeManagement.getOfficialDocument("srs");
        firstSRS.display();

        secondSRS = prototypeManagement.getOfficialDocument("srs");
        secondSRS.display();

        log.info("【在原型管理器克隆的SRS地址是否相同:】 {}",firstSRS==secondSRS);
        log.info("【在原型管理器克隆的SRS内容是否相同:】 {}",firstFAR.equals(secondSRS));

    }
}

⑤测试结果


13:25:08.513 [main] INFO cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement.FAR - 《可行性分析报告》
13:25:08.534 [main] INFO cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement.FAR - 《可行性分析报告》
13:25:08.534 [main] INFO cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement.Test - 【在原型管理器克隆的FAR地址是否相同:】 false
13:25:08.537 [main] INFO cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement.Test - 【在原型管理器克隆的FAR内容是否相同:】 false
13:25:08.537 [main] INFO cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement.SRS - 【软件规格说明书】
13:25:08.537 [main] INFO cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement.SRS - 【软件规格说明书】
13:25:08.537 [main] INFO cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement.Test - 【在原型管理器克隆的SRS地址是否相同:】 false
13:25:08.540 [main] INFO cn.lframe.designmode.prototypepattern.fourthmethodprototypemanagement.Test - 【在原型管理器克隆的SRS内容是否相同:】 false

总结

原型模式作为一种快速创建大量相同或相似的对象的方式,在软件开发中应用较为广泛,很多软件提供的复制(Ctrl+C)和粘贴(Ctrl+V)操作就是原型模式的典型应用。

1.主要优点
(1) 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。

(2) 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。

(3) 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。

(4) 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。

2.主要缺点
(1) 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。

(2) 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

3.适用场景
(1) 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。

(2) 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。

(3) 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

参考:https://blog.csdn.net/lovelion/article/details/7424623
参考:https://blog.csdn.net/lovelion/article/details/7424620
参考:https://blog.csdn.net/lovelion/article/details/7424559
更多详细请看:https://blog.csdn.net/lovelion/article/details/17517213

猜你喜欢

转载自blog.csdn.net/m0_37884977/article/details/80265635