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)同一个对象可能会供其他调用者同时调用访问时。