Skip to content

Latest commit

 

History

History
323 lines (247 loc) · 9.33 KB

04-原型模式.md

File metadata and controls

323 lines (247 loc) · 9.33 KB

原型模式

应用场景

​ 你一定遇到过大篇幅的getter、setter赋值的场景。例如这样的代码:

public void setParam(ExamPaperVo vo){
  ExamPaper examPaper = new ExamPaper();
  //试卷主键 examPaper.setExamination
  PaperId(vo.getExaminationPaperId()); 
  //剩余时间
  curForm.setLeavTime(examPaper.getLeavTime());
  //单位主键 
  curForm.setOrganizationId(examPaper.getOrganizationId()); 
  //考试主键
  curForm.setId(examPaper.getId());
  //考场主键 
  curForm.setExamroomId(examPaper.getExamroomId()); 
  //用户主键 
  curForm.setUserId(examPaper.getUserId());
  //专业 
  curForm.setSpecialtyCode(examPaper.getSpecialtyCode()); 
  //岗位 		
  curForm.setPostionCode(examPaper.getPostionCode()); 
  //等级
  curForm.setGradeCode(examPaper.getGradeCode()); 
  //考试开始时间 
  curForm.setExamStartTime(examPaper.getExamStartTime()); 
  //考试结束时间
  curForm.setExamEndTime(examPaper.getExamEndTime());
  //单选题重要数量 
  curForm.setSingleSelectionImpCount(examPaper.getSingleSelectionImpCount()); 
  //多选题重要数量 
  curForm.setMultiSelectionImpCount(examPaper.getMultiSelectionImpCount()); 
  //判断题重要数量 
  curForm.setJudgementImpCount(examPaper.getJudgementImpCount());
  //考试时间
  curForm.setExamTime(examPaper.getExamTime());
  //总分
  curForm.setFullScore(examPaper.getFullScore());
  //及格分
  curForm.setPassScore(examPaper.getPassScore());
  //学员姓名
  curForm.setUserName(examPaper.getUserName());
  //分数
  curForm.setScore(examPaper.getScore());
  //是否及格
  curForm.setResult(examPaper.getResult()); 
  curForm.setIsPassed(examPaper.getIsPassed());
  //单选答对数量 
  curForm.setSingleOkCount(examPaper.getSingleOkCount()); 
  //多选答对数量 
  curForm.setMultiOkCount(examPaper.getMultiOkCount()); 
  //判断答对数量 
  curForm.setJudgementOkCount(examPaper.getJudgementOkCount());
  //提交试卷
  service.submit(examPaper); 
}

代码工整,命名规范,注释也全面。但是这样的代码是纯体力劳动。原型模式能帮我们解决这样的问题。

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

原型模式主要使用于以下场景:

  1. 类初始化消耗资源较多。
  2. new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)。
  3. 构造函数比较复杂。
  4. 循环体内生产大量对象时候。

在Spring中,原型模式应用得非常广泛。例如scope="prototype",在我们经常用的JSON.parseObject()也是一种原型模式。下面,我们来看看原型模式类结构图:

image-20190917061423974

简单克隆

​ 一个标准的原型模式代码,应该是这样设计的。先创建原型prototype接口:

package com.wenbin.design.pattern.prototype;

public interface Prototype {
    Prototype clone();
}

创建具体需要克隆的对象ConcretePrototype:

public class ConcretePrototypeA implements Prototype {

    private int age;
    private String name;
    private List hobbies;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public List getHobbies() {
        return hobbies;
    }

    public void setHobbies(List hobbies) {
        this.hobbies = hobbies;
    }

    public Prototype clone() {
        ConcretePrototypeA concretePrototypeA = new ConcretePrototypeA();

        concretePrototypeA.setAge(this.age);
        concretePrototypeA.setName(this.name);
        concretePrototypeA.setHobbies(this.hobbies);
        return concretePrototypeA;
    }


}

创建Client对象:

public class Client {
    private Prototype prototype;

    public Client(Prototype prototype) {
        this.prototype = prototype;
    }

    public Prototype startClone(Prototype concretePrototype) {
        return concretePrototype.clone();
    }
}

测试代码:

public class PrototypeTest {
    public static void main(String[] args) {
        // 创建一个具体的需要克隆的对象
        ConcretePrototypeA concretePrototypeA = new ConcretePrototypeA();
        // 填充属性
        concretePrototypeA.setAge(18);
        concretePrototypeA.setName("prototypeA");
        List<String> hobbies = new ArrayList<String>();
        concretePrototypeA.setHobbies(hobbies);
        System.out.println(concretePrototypeA);

        // 创建Client对象准备开始克隆
        Client client = new Client(concretePrototypeA);
        ConcretePrototypeA concretePrototypeAClone = (ConcretePrototypeA) client.startClone(concretePrototypeA);

        System.out.println(concretePrototypeAClone);
        System.out.println("克隆对象中的引用类型地址值:" + concretePrototypeAClone.getHobbies());
        System.out.println("原对象中的引用类型地址值:" + concretePrototypeA.getHobbies());
        System.out.println("对象地址比较:"+(concretePrototypeAClone.getHobbies() == concretePrototypeA.getHobbies()));
    }
}

运行结果:

com.wenbin.design.pattern.prototype.ConcretePrototypeA@60e53b93
com.wenbin.design.pattern.prototype.ConcretePrototypeA@5e2de80c
克隆对象中的引用类型地址值:[]
原对象中的引用类型地址值:[]
对象地址比较:true

Process finished with exit code 0

从测试结果看出hobbies的引用地址的相同的,意味着复制的不是值,而是引用地址。这样的话,如果我们修改任意一个对象中的属性值,concretePrototypeA和concretePrototypeClone的hobbies值都会变。这就是我们常说的浅科通。只是完整复制了值类型数据,没有复制引用对象。换言之,所有的引用对象仍然指向原来的对象,嫣然不是我们想要的结果。下面我们来看看深度克隆。

深度克隆

​ 我们换一个场景,大家都知道齐天大圣。首先它是一只猴子,有七十二般变化,拔一根毫毛就可以吹出千万个泼猴,手里还拿着金箍棒,金箍棒可以变大变小。这就是我们耳熟能详的原型模式的经典体现。

创建原型猴子Monkey类:

package com.wenbin.design.pattern.prototype.deep;

import java.util.Date;

public class Monkey {
    public int height;
    public int weight;
    public Date birthday;
}

创建引用对象金箍棒类:

package com.wenbin.design.pattern.prototype.deep;

import java.io.Serializable;

public class JinGuBang implements Serializable {
    public float h = 100;
    public float d = 10;

    public void big() {
        this.d *= 2;
        this.h *= 2;
    }

    public void small() {
        this.d /= 2;
        this.h /= 2;
    }
}

创建具体的对象齐天大圣类:

package com.wenbin.design.pattern.prototype.deep;

import java.io.*;
import java.util.Date;

public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {

    public JinGuBang jinGuBang;

    public QiTianDaSheng() {
        // 只是初始化
        this.birthday = new Date();
        this.jinGuBang = new JinGuBang();
    }

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

    public Object deepClone() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            QiTianDaSheng copy = (QiTianDaSheng) ois.readObject();
            copy.birthday = new Date();
            return copy;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public QiTianDaSheng shallowClone(QiTianDaSheng target) {
        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();

        qiTianDaSheng.height = target.height;
        qiTianDaSheng.weight = target.height;
        qiTianDaSheng.jinGuBang = target.jinGuBang;
        qiTianDaSheng.birthday = new Date();

        return qiTianDaSheng;
    }
}

测试代码:

package com.wenbin.design.pattern.prototype.deep;

public class DeepCloneTest {

    public static void main(String[] args) {
        QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();

        try {
            QiTianDaSheng clone = (QiTianDaSheng) qiTianDaSheng.clone();
            System.out.println("深克隆:" + (qiTianDaSheng.jinGuBang == clone.jinGuBang));
        } catch (Exception e) {
            e.printStackTrace();
        }

        QiTianDaSheng q = new QiTianDaSheng();
        QiTianDaSheng n = q.shallowClone(q);

        System.out.println("浅克隆:" + (q.jinGuBang == n.jinGuBang));
    }
}

运行结果:

深克隆:false
浅克隆:true

Process finished with exit code 0

克隆破坏单例

​ 如果我们克隆目标的对象是单例对象,那意味着,深克隆就会破坏单例。实际上防止克隆破坏单例解决思路很简单,禁止深克隆即可。要么我们的单例类不实现Cloneable接口,要么我们重新clone()方法,在clone方法中返回单例对象即可。