05-日常工作日的一天:原型模式

5.1模式背景故事

  以我们日常工作中朝九晚五的一天生活为背景对象。

  7:00——起床

  7:30——坐公交车

  8:30——早餐,到公司

  12:00——午餐,午休

  13:30——开始下午工作

  17:30——下班回家

5.2模式定义

  原型设计模式(Prototype Pattern),用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

  本章讲述的是创建型模式中一种比较特殊的模式——原型模式,这个模式有个最大的特点就是克隆一个现有的对象,这个克隆的结果有两种,一种是浅复制,另一种是深复制。随后会探讨浅复制和深复制的原理。我们都知道创建型模式一般用来创建一个新的对象,然后使用这个对象完成一些对象的操作,我们通过原型模式可以快速地创建一个对象而不需要提供专门的new()操作,这无疑是一种非常有效的方式,可以快速创建一个新的对象。

5.3模式分析

5.3.1原型模式的静态建模

  原型设计模式的概念就是:用原型实例指定创建对象的种类,即我们一天工作日的生活,如果要生成多天的生活,只要通过复制这些原型创建新的对象就可以了,而不必每次都new()一个实例对象。这样将大大节省创建对象所消耗的各种资源。原型设计模式的结构图如下所示:

  

  在上面的原型模式结构图中,首先需要一个原型模式的抽象,接口和抽象类都行,在抽象中含有clone方法,具体的原型实现类具体实现抽象的clone方法,返回具体的类型。客户端使用抽象原型,传入的则是具体的原型对象类型,然后通过原型对象的clone方法产生一个与原型对象一样的克隆对象,而不需要使用new产生对象。

5.4原型模式实现

5.4.1原型的建立

  注:原型类必须实现Cloneable接口。

package com.prototype.pojo;

/**
 * Created by lsq on 2018/3/14.
 * 日常生活类
 */
public class DayLife implements Cloneable{

    //起床
    private String getUp;
    //坐公交
    private String byBus;
    //下车,买早餐
    private String getFood;
    //中午小憩
    private String noon;
    //下午开始工作
    private String afternoonWork;
    //下班回家
    private String goHome;
    //晚上休闲
    private String night;

    public String getGetUp() {
        return getUp;
    }

    public void setGetUp(String getUp) {
        this.getUp = getUp;
    }

    public String getByBus() {
        return byBus;
    }

    public void setByBus(String byBus) {
        this.byBus = byBus;
    }

    public String getGetFood() {
        return getFood;
    }

    public void setGetFood(String getFood) {
        this.getFood = getFood;
    }

    public String getNoon() {
        return noon;
    }

    public void setNoon(String noon) {
        this.noon = noon;
    }

    public String getAfternoonWork() {
        return afternoonWork;
    }

    public void setAfternoonWork(String afternoonWork) {
        this.afternoonWork = afternoonWork;
    }

    public String getGoHome() {
        return goHome;
    }

    public void setGoHome(String goHome) {
        this.goHome = goHome;
    }

    public String getNight() {
        return night;
    }

    public void setNight(String night) {
        this.night = night;
    }

    /**
     * 打印输出日常生活信息
     */
    public void print(){
        System.out.println(this.getGetUp());
        System.out.println(this.getByBus());
        System.out.println(this.getGetFood());
        System.out.println(this.getNoon());
        System.out.println(this.getAfternoonWork());
        System.out.println(this.getGoHome());
        System.out.println(this.getNight());
    }

    /**
     * clone方法
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public DayLife clone(){
        try {
            //调用超类的clone方法(所有类都是Object的子类)
            return (DayLife) super.clone();
        }catch (Exception e){

        }
        return null;
    }
}

5.4.2创建生成原型对象的工厂

1)抽象工厂——ILifeFactory工厂类

package com.prototype.factory;

import com.prototype.pojo.DayLife;

/**
 * Created by lsq on 2018/3/15.
 * 工厂类
 */
public interface ILifeFactory {

    /**
     * 生产DayLife对象
     * @return
     */
    public DayLife getNewInstance();

}

2)具体工厂——LifeFactoryImpl工厂实现类

package com.prototype.factory.impl;

import com.prototype.factory.ILifeFactory;
import com.prototype.pojo.DayLife;

/**
 * Created by lsq on 2018/3/15.
 * 工厂实现类
 */
public class LifeFactoryImpl implements ILifeFactory{

    //DayLife实例用于初始化
    private static DayLife dayLife = null;

    /**
     * getNewInstance方法实现:
     * 首先判断dayLife是否为null:
     * 如果是null,则使用new创建一个DayLife对象,同时设置初始内容,并赋值给dayLife对象实例,然后返回;
     * 如果不是null,则使用dayLife的clone方法产生一个新对象并复制给dayLife对象,然后返回
     * @return
     */
    @Override
    public DayLife getNewInstance() {
        //判断dayLife是否为null
        if (dayLife==null){
            //注意:new这个只使用一次
            System.out.println(" new DayLife !");
            dayLife = new DayLife();
            dayLife.setGetUp("7:00起床");
            dayLife.setByBus("7:30坐公交车");
            dayLife.setGetFood("8:30下公交,买早餐,去公司");
            dayLife.setNoon("12:00午餐,午休");
            dayLife.setAfternoonWork("13:30开始下午工作");
            dayLife.setGoHome("17:30下班回家");
            dayLife.setNight("晚上个人时间");
        }else {
            //输出使用clone方法产生的对象
            System.out.println(" clone DayLife !");
            dayLife = dayLife.clone();
        }
        return dayLife;
    }
}

5.4.3运行测试

import com.prototype.factory.ILifeFactory;
import com.prototype.factory.impl.LifeFactoryImpl;
import com.prototype.pojo.DayLife;

/**
 * Created by lsq on 2018/3/13.
 *
 */
public class MainApp {

    public static void main(String[] args) {
        //创建工厂
        ILifeFactory lifeFactory = new LifeFactoryImpl();
        //打印输出DayLife默认内容
        lifeFactory.getNewInstance().print();

        //再次获得DayLife,修改起床时间
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~");
        DayLife dayLife = lifeFactory.getNewInstance();
        dayLife.setGetUp("早上赖床了,7:10才起床!");
        dayLife.print();

        //再次获得DayLife,修改起床时间
        System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~");
        DayLife dayLife2 = lifeFactory.getNewInstance();
        dayLife2.setGetUp("早上赖床了,7:20才起床!");
        dayLife2.print();
    }

}

运行结果:

 

5.5设计原则

  原型模式的核心是一个clone方法,通过这个方法进行对象的复制,在Java中,提供了一个Cloneable接口来标示这个对象是可复制的。为什么说是“标示”呢?查看Cloneable类源码,会发现该接口中一个方法也没有,这个接口的作用就是一个标示,只有实现该接口的对象才有可能被复制!那,如何从“有可能被复制”变为“可以被复制”呢?方法就是覆盖超类的clone()方法!

  在使用原型模式的时候,要注意以下几个事项。

1、克隆对象时,对象的构造方法不执行

  只有new时构造方法才被执行,clone的时候是不会执行构造方法的。为什么呢?因为在使用Object类的clone()方法时,是从内存中直接复制二进制流,重新分配内存块给克隆对象。

2、浅复制和深复制

  先看一个例子:

import java.util.ArrayList;

/**
 * Created by lsq on 2018/3/15.
 *
 */
public class Test implements Cloneable {

    //私有属性
    private ArrayList<String> nameList = new ArrayList<>();

    //添加内容
    public void add(String s){
        this.nameList.add(s);
    }

    //获得ArrayList对象
    public ArrayList get(){
        return this.nameList;
    }

    //clone方法
    @Override
    public Test clone(){
        try {
            return (Test)super.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        //创建test对象
        Test test = new Test();
        //设置test对象内容
        test.add("aa");
        test.add("bb");
        //打印显示test中的nameList内容
        System.out.println("test:"+test.get());

        //克隆test对象到test2对象
        Test test2 = test.clone();
        //添加“cc”到test2中
        test2.add("cc");
        //打印显示test2中的nameList内容
        System.out.println("test2:"+test2.get());

        //打印显示test中的nameList内容
        System.out.println("test:"+test.get());
    }
}

运行结果:

  我们发现对克隆对象的修改,影响到了原始对象的内容!这可不是我们想要看到的结果!这就是浅复制所带来的结果。

1)浅复制

  Object类的clone方法只是复制对象的原始数据类型,如int、float、String等,对于数组和对象引用是不会复制的。因此,浅复制是有风险的。要做到完全复制,就需要深复制了。

2)深复制

  深复制是指对于对象中的数组和对象引用也做复制的行为,从而达到将对象完全复制的效果。例如,上面的例子中,我们修改一下Test类的clone方法:

    //clone方法
    @Override
    public Test clone(){
        try {
            Test test = (Test)super.clone();
            test.nameList = (ArrayList<String>) this.nameList.clone();
            return test;
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return null;
    }

  运行结果:

  从结果来看,test和test2两个对象已经没有任何联系了。从以上内容来看,Object的clone方法只是做浅复制,也就是直接复制类的字段内容,并不管属性字段对象的内容。我们可以在重载的clone方法中进行类的“深复制”,从而得到我们想要的结果。

5.6使用场合

  1)产生对象过程比较复杂,初始化需要许多资源;

  2)希望框架原型和产生对象分开时;

  3)同一个对象可能会供其他调用者同时调用访问时。

posted @ 2018-03-15 12:01  shanquan  阅读(197)  评论(1编辑  收藏  举报