创建模式中的原始(Prototype)模式

来源:互联网 发布:现金流量适合比率算法 编辑:程序博客网 时间:2024/04/30 13:56

创建模式中的原始(Prototype)模式

原始模型模式属于对象的创建模式。通过给出一个原始对象来指明所要创建的对象的类型。然后用复制这个原型对象的办法创建出更多同类型的对象。

原始模型模式: Java语言的构件模型直接支持原始模型模式。所有的JavaBean都继承自java.lang.Object,Object类提供一个clone(),可以将一个JavaBean对象复制一份。但是,这个JavaBean必须实现一个标示接口(Cloneable),标明这个JavaBean支持复制。如果一个对象没有实现这个接口而调用clone()方法,Java编译器就会抛出CloneNotSupportedException异常。

变量、对象以及对象的引用

Java语言中,对象就是类的实例。在一般情况下,当把一个类实例化时,此类的所有的成员,包括变量和方法,都被复制到属于此数据类型的一个新的实例中去。对象的创建和对象的引用时分隔开来的。

Java对象的复制

java.lang.Object.clone()方法  Java的所有类都是从java.lang.Object类继承而来的。而Object类提供下面的对象的方法对对象进行复制

protected Object clone() 子类当然也可以把这个方法置换掉,提供满足自己需要的复制方法,对象的复制有一个基本问题,就是对象通常都有对其他的对象的引用。当使用Object类的clone()方法来复制一个对象时,此对象对其他对象的引用也同时会被复制一份。Java语言提供的Cloneable接口只起一个作用,就是在运行时期通知Java虚拟机可以安全地在这个类上使用clone()方法,通过调用这个clone()方法可以得到一个对象的复制。由于Object类本身并不实现Cloneable接口,因此如果所考虑的类没有实现Cloneable接口时,调用clone方法会抛出CloneNotaSupportedException异常。of course ,系统可以在PandaToClone里面置换掉java.lang.Object提供的clone()方法,以便控制复制的过程。

package com.test.clone;

 

public class PandaToClone implements Cloneable {

   

    private int height;

   

    private int weight;

   

    private int age;

 

    public PandaToClone(int height, int weight) {

       this.age = 0;

       this.height = height;

       this.weight = weight;

    }

 

    public int getHeight() {

       return height;

    }

 

    public void setHeight(int height) {

       this.height = height;

    }

 

    public int getWeight() {

       return weight;

    }

 

    public void setWeight(int weight) {

       this.weight = weight;

    }

 

    public int getAge() {

       return age;

    }

 

    public void setAge(int age) {

       this.age = age;

    }

   

    /*public Object clone() {

       PandaToClone temp = new PandaToClone(height, weight);

       temp.setAge(age);

       return (Object)temp;

    }*/

 

    public static void main(String[] args) throws CloneNotSupportedException {

       PandaToClone thisPanda = new PandaToClone(15, 25);

       PandaToClone thatPanda = (PandaToClone)thisPanda.clone();

       System.out.println("Age of thisPanda: " + thisPanda.getAge());

       System.out.println("                height: " + thisPanda.getHeight());

       System.out.println("                weight: " + thisPanda.getWeight());

      

       System.out.println("Age of thatPanda: " + thatPanda.getAge());

       System.out.println("                height: " + thatPanda.getHeight());

       System.out.println("                weight: " + thatPanda.getWeight());

       System.out.println(thisPanda == thatPanda);

    }

   

}

Age of thisPanda: 0

                height: 15

                weight: 25

Age of thatPanda: 0

                height: 15

                weight: 25

false

克隆满足的条件:

clone()方法将对象复制了一份并返回给调用者。所谓“复制”的含义与clone()方法时怎么实现的有关。一般而言,clone()方法满足以下的描述:

     对任何对象x,都有:x.clone() != x。换言之,克隆对象与原对象不是同一个对象。

     对任何的对象x,都有:x.clone.getClass == x.getClass(),换言之,克隆对象与原对象的类型一样。

     如果对象的xequals()方法定义恰当的话,那么x.clone().equals(x)应当是成立的。语言的设计师在设计自己的clone()方法时,也应当遵守这三个条件。

原始模型模式的结构:

原始模型模式有两种表现形式:第一种是简单形式,第二种是登记形式。这两种表现形式仅仅是原始模型模式的不同实现,但是由于他们的区别影响了模式结构的细节。

简单形式的原始模型模式

package com.test.clone;

 

public interface Prototype extends Cloneable {

   

    Object clone();

}

 

package com.test.clone;

 

public class ConcretePrototype implements Prototype {

   

    /**

     * 克隆方法

     */

    public Object clone() {

       try {

           return super.clone();

       } catch (CloneNotSupportedException e) {

           return null;

       }

    }

 

}

 

package com.test.clone;

 

public class Client {

   

    private static Prototype prototype;

 

    public static void main(String[] args) {

       prototype = new ConcretePrototype();

       Prototype p2 = (Prototype)prototype.clone();

      

    }

 

}

这种形式涉及到三个角色:

     客户(Client)角色:客户类提出创建对象的请求。

     抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需要的接口。

     具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所需求的接口。

登记形式的原始模型模式

第二种形式的原始模型模式

package com.test.clone;

 

import java.util.Vector;

 

public class PrototypeManager {

   

    private Vector<Prototype> objects = new Vector<Prototype>();

   

    /**

     * 聚集管理方法:增加一个新的对象

     * @param object

     */

    public void add(Prototype object) {

       objects.add(object);

    }

   

    /**

     * 聚集管理方法:取出聚集中的一个对象

     * @param i

     * @return

     */

    public Prototype get(int i) {

       return (Prototype)objects.get(i);

    }

   

    /**

     * 聚集管理方法:给出聚集的大小

     * @return

     */

    public int getSize() {

       return objects.size();

    }

 

}

两种形式的比较:

如果需要创建的原型对象数目较少而且比较固定的话,可以采用第一种形式,也即简单形式的原始模型模式。在这种情况下,原型对象的引用可以由客户端自己保存。

如果要创建的原型对象数目不固定的话,可以采取第二种形式,也即登记形式的原始模型模式。在这种情况下,客户端并不保存对原型对象的引用,这个任务被交给管理员对象。在复制一个对象之前,客户端可以查看管理员对象是否已经有一个满足要求的原型对象。如果有,可以直接从管理员类取得这个对象的引用;如果没有,客户端就需要自行复制此原型对象。

模式的实现:深复制(深克隆)和浅复制(浅克隆)

浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,而所有的其他对象的引用都仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

深复制(深克隆) 被复制对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过来的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用对象都复制了一遍,而这种对被引用的对象的复制叫做间接复制。

深复制要深入多少层,是一个不易确定的问题。在决定以深复制方式复制一个对象的时候,必须决定对间接复制的对象采取浅复制还是继续采用深复制。因此,在采取深复制是,需要决定多深才算深,此外,在复制的过程中,很可能会出现循环引用的问题,必须小心处理。

把对像写到流里的过程是串行化(Serilization)过程,但是在Java程序师的圈子里又非常形象地成为“冷冻”或者“腌咸菜(pickling)过程”;而把对像从流里读出来的过程叫做并行化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出写到流里的是一个对象的拷贝,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个拷贝,Java咸菜还可以回鲜。

Java语言里深复制一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个拷贝)写到一个流里)(腌成咸菜),再从流里(把咸菜回鲜),便可以重建对象。

这样做的前提是:对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象是否设成transient,从而将之排除在复制过程之外。

浅复制虽然比深复制更容易实现,因为Java语言的所有类都会继承一个clone()方法,而这个clone() 方法所做的正是浅复制。

package com.test.clone;

 

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;

 

public class DeepCloneStudent implements Serializable {

   

    private int id;

   

    private String name;

   

    public Object deepClone() throws Exception {

       ByteArrayOutputStream bo = new ByteArrayOutputStream();

       ObjectOutputStream oo = new ObjectOutputStream(bo);

       oo.writeObject(this);

       ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());

       ObjectInputStream oi = new ObjectInputStream(bi);

       return oi.readObject();

    }

 

    public static void main(String[] args) throws Exception  {

       DeepCloneStudent st = new DeepCloneStudent();

       st.id = 1;

       st.name = "li";

       DeepCloneStudent st2 = (DeepCloneStudent)st.deepClone();

       System.out.println(st2.id);

       System.out.println(st2.name);

    }

 

}

使用原始模型模式的场景:假设一个系统的产品类是动态加载的,而且产品具有一定的等级结构。

原始模型模式的优点和缺点:

抽象工厂模式有许多与原始模型模式和建造模式相同的效果,包括客户端不知道具体产品类,而只知道抽象产品类,客户端不需要知道这么多产品的具体产品名称。如果有新的产品类加入,客户端不需要进行改造就可直接使用。

优点:

     原始模型模式允许动态地增加或减少产品类。由于创建产品类实例的方法是产生类内部具有的,因此,增加新产品对整个结构没有影响。

     原始模型模式提供简化的创建结构。工厂方法模式常常需要一个与产品类等级结构相同的等级结构,而原始模型模式就不需要这样。对于Java设计师来说,原始模型模式又有其特有的方便之处,因为Java语言天生就将原始模型模式设计到了语言模型里面。善于利用原始模型模式和Java语言的特点,可以事半功倍。

     具有给一个应用软件动态加载功能的能力。例如,一个分析Web服务器的记录文件的应用软件,针对每一种记录文件格式,都可以由一个相应的“格式类”负责。如果出现了应用软件所不支持的新的Web服务器,只需要提供一个格式类的克隆,并在客户端登记即可,而不必给每个软件的用户提供一个全新的软件包。

     产品类不需要非得有任何事先确定的等级结构,因为原始模型模式适用于任何等级结构。

原始模型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

Java的串行化功能:

Java语言从1.0升级到1.1版本时,Java语言的功能在很多方面得到了很大的提高,串行化(Serialization)就是那时候引进来的新的功能。用一句话来讲,串行化使得一个程序可以把一个完整的对象写到一个Byte流里面,或者从一个Byte流里面读出一个事先存储在里面的完整对象;串行化可以把Java对象和原始数据类型转换成一个适合于某种网络或文件系统的Byte流。