一、概念

  用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

二、模式动机

  当已有一个对像,暂且称之为原型对象,需要一个新的对像,该对像和已有的原型对像具有相同的类型,且里面的属性大部分相同,或者只有个别不同时,这时就可以用原型模式,克隆原型对像,产生一个新的对像,并对新的对像属性进行适当的修改,已适应系统需求。比如新生产的同一批次同一型号的笔记本电脑,如果每个电脑都是一个实例对像,这些电脑配置都是相同的,唯一不同可能是序列号不同,如何实例化所有的电脑,这时就可以用一个原型电脑,去克隆出所有的电脑,只需对克隆出来的新电脑设置正确的序列号就可以了。

三、模式的结构

  

   角色分析:

    1.IPrototype:声明一个克隆自身的接口

    2.ConcretePrototype(ConcretePrototypeA,ConcretePrototypeA): 实现了IPrototype接口, 这些类真正实现了克隆自身的功能

    3.Client:让一个原型对象克隆自身产生一个新的对象。

  

样例代码:

package prototype;

/**
 * 声明一个克隆自身的接口,所有实现该接口的类实例,都可以克隆自身产生一个新的对象
* @ClassName: IPrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:47:35 
*
 */
public interface IPrototype {
    
    /**
     * 实现克隆自身的接口方法
    * @Title: clone 
    * @param @return   
    * @return IPrototype    
    * @throws
     */
    public IPrototype clone();

}
package prototype;

/**
 * 实现了克隆接口的具体实现对象
* @ClassName: ConcretePrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:51:13 
*
 */
public class ConcretePrototypeA implements IPrototype {

    /**
     * 实现克隆功能的具本方法
     */
    @Override
    public IPrototype clone() {
        //最简单的方式就是new 一个新对像,并一一将自已的属性值复制到新的对像里面
        IPrototype cloneObj=new ConcretePrototypeA();
        //将本对像的属性值赋于新的对像
        //如   cloneObj.setProperty(this.propety); 等
        return cloneObj;
    }

}
package prototype;

/**
 * 实现了克隆接口的具体实现对象
* @ClassName: ConcretePrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:51:13 
*
 */
public class ConcretePrototypeB implements IPrototype {
    
    /**
     * 实现克隆功能的具本方法
     */
    @Override
    public IPrototype clone() {
        //最简单的方式就是new 一个新对像,并一一将自已的属性值复制到新的对像里面
        IPrototype cloneObj=new ConcretePrototypeB();
        //将本对像的属性值赋于新的对像
        //如   cloneObj.setProperty(this.propety); 等
        return cloneObj;
    }

}
package prototype;

/**
 * 客户端程序,从已有实例克隆出一个新的对象
* @ClassName: Client 
* @author beteman6988
* @date 2017年10月23日 下午10:06:18 
*
 */
public class Client {
    private IPrototype instance=new ConcretePrototypeA();
    
    
    private void operation() {
        //从已有实例 instance 克隆出一个新的对象 cloneObj
        IPrototype  cloneObj=instance.clone();
    }
    
}

  

 关于浅度克隆、深度克隆及java对该原型模式的支持

  1.浅度克隆与深度克隆:在讲原型模式是,浅度克隆与深度克隆是逃不开的话题,上面的示例都是浅度克隆,那么什么是浅度克隆和深度克隆呢?

    浅度克隆:只负责按值传递的数据,如基本数据类型和String  ,如果是引用,则原型对像和克隆出的对像指的是同一个引用地址。

      深度克隆:除了浅度克隆要克隆的值外,引用对像也会被克隆,克隆出的对像和原型对像中的引用是不同的,指上不同的地址空间。

  2. java对浅度克隆的支持:java.lang.Object.clone()方法,该方法为protected native方法,表明继承于他的类都可以调用该方法,又由于java中的所有类都继承于java.lang.Object,所以说java中的所有类都可以以super.clone()的方式调用java.lang.Object.clone()方法。当子类通过调用java.lang.Object.clone()方法实现克隆时,子类必须实现Cloneable标识接口,该标识接口的作用就是在运行时通知java虚拟机可以安全的在这个类上使用clone()方法,通过该方法得到一个对像的克隆,如下图所示:

            

  代码如下:

  

package prototype.cloneable;

/**
 * 声明一个克隆自身的接口,所有实现该接口的类实例,都可以克隆自身产生一个新的对象
* @ClassName: IPrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:47:35 
*
 */
public interface IPrototype extends Cloneable {
    
    /**
     * 实现克隆自身的接口方法
    * @Title: clone 
    * @param @return   
    * @return IPrototype    
    * @throws
     */
    public IPrototype clone();

}
package prototype.cloneable;
/**
 * 实现了克隆接口的具体实现对象
* @ClassName: ConcretePrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:51:13 
*
 */
public class ConcretePrototypeA implements IPrototype {

    /**
     * 实现克隆功能的具本方法
     */
    @Override
    public IPrototype clone() {
        
        try {
            return     (IPrototype)super.clone();
            
        } catch ( Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
    }
}
package prototype.cloneable;

/**
 * 客户端程序  跟据已有实例,克隆出一个新的实例
* @ClassName: Client 
* @author beteman6988
* @date 2017年10月23日 下午11:37:18 
*
 */
public class Client {
    private IPrototype instance=new ConcretePrototypeA();
    public void option() {
        IPrototype p=instance.clone();
    }
    
}

     3.java对深度克隆的支持:在java里面利用串行化Serilization可以实现深度克隆,他要求原型类及原型类里面的引用类都需要实现Serializable标识接口,他的实现思想是通过将原型对像写到流里面,然后再从流里读出来重建对像。还是基于上面的例子,如下图:

      

package prototype;

import java.io.Serializable;

public class A  implements Serializable {
    
}
package prototype;

import java.io.Serializable;

/**
 * 声明一个克隆自身的接口,所有实现该接口的类实例,都可以克隆自身产生一个新的对象
* @ClassName: IPrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:47:35 
*
 */
public interface IPrototype extends Serializable {
    
    /**
     * 实现克隆自身的接口方法
    * @Title: clone 
    * @param @return   
    * @return IPrototype    
    * @throws
     */
    public IPrototype clone();

}
package prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 实现了克隆接口的具体实现对象
* @ClassName: ConcretePrototype 
* @author beteman6988
* @date 2017年10月23日 下午9:51:13 
*
 */
public class ConcretePrototypeA implements IPrototype {
    
    private A a =new A();

    /**
     * 实现克隆功能的具本方法
     */
    @Override
    public IPrototype clone() {
        
        try {
            //将对像写入流中
            ByteArrayOutputStream bo=new ByteArrayOutputStream();
            ObjectOutputStream oo=new ObjectOutputStream(bo);
            oo.writeObject(this);
            
            //将对像从流中读出
            ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
            ObjectInputStream oi=new ObjectInputStream(bi);
            return (IPrototype)oi.readObject();
            
        } catch ( Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
    }
}
package prototype;

/**
 * 客户端程序  
* @ClassName: Client 
* @author beteman6988
* @date 2017年10月23日 下午11:37:18 
*
 */
public class Client {
    
    public static void main(String[] args) {
        IPrototype prototype=new ConcretePrototypeA();
        IPrototype copy= prototype.clone();
    }
}

通过调试见下图,可以看出copy对象及内部的a 引用对象与原型对象是不同的对象,如下图示:

        

 

    4.java通过逐级浅克隆实现深度克隆:通过java.lang.Object.clone()对java对像及内部的引用逐级克隆也是可以实现的,难点只是无法确定克隆的层次,因为引用里面还有可能有引用,引用的层次无法确定。 

四、模式样例

  现实生活中复印机和细胞分裂就是很贴切的例子,当我们将一份文件放到复印机中,复印机可以将原件自身复印(克隆)出另一份文件,两份文件内容相同,但是是不同的两个实体。如下图所示:

               

 

  

package prototype;

/**
 * 具有复印功能的接口
* @ClassName: CopyAble 
* @author beteman6988
* @date 2017年10月23日 下午11:19:45 
*
 */
public interface CopyAble {
    
    public CopyAble copy();

}
/**
 * 可以复制的一张纸
* @ClassName: Paper 
* @author beteman6988
* @date 2017年10月23日 下午11:28:57 
*
 */
public class Paper implements CopyAble {
    
    private String header;  //文件头
    private String content; //文件内容
    private String footer;  //文件尾

    
    /**
     * 复制出内容一样的一张纸
     */
    @Override
    public CopyAble copy() {
        Paper anotherPaper=new Paper();
        anotherPaper.setHeader(this.header);
        anotherPaper.setContent(this.content);
        anotherPaper.setFooter(this.footer);
        return anotherPaper;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getFooter() {
        return footer;
    }

    public void setFooter(String footer) {
        this.footer = footer;
    }
    
    
}
/**
 * 可以复制的一张纸
* @ClassName: Paper 
* @author beteman6988
* @date 2017年10月23日 下午11:28:57 
*
 */
public class Paper implements CopyAble {
    
    private String header;  //文件头
    private String content; //文件内容
    private String footer;  //文件尾

    
    /**
     * 复制出内容一样的一张纸
     */
    @Override
    public CopyAble copy() {
        Paper anotherPaper=new Paper();
        anotherPaper.setHeader(this.header);
        anotherPaper.setContent(this.content);
        anotherPaper.setFooter(this.footer);
        return anotherPaper;
    }

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getFooter() {
        return footer;
    }

    public void setFooter(String footer) {
        this.footer = footer;
    }
    
    
}
/**
 * 可复制的一张照片
* @ClassName: Picture 
* @author beteman6988
* @date 2017年10月23日 下午11:30:51 
*
 */
public class Picture implements CopyAble {

    private String data; //照片内容
    
    /**
     * 复制出内容一模一样的一张照片
     */
    @Override
    public CopyAble copy() {
        // TODO Auto-generated method stub
        return null;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
    
}
package prototype;

/**
 * 复印机类,可以复印一切可以复印的东西(即实现CopyAble的实现类)
* @ClassName: CopyMachine 
* @author beteman6988
* @date 2017年10月23日 下午11:34:35 
*
 */
public class CopyMachine {
    
    /**
     * 对传入的可复制对像进行复制
    * @Title: runCopy 
    * @param @param source
    * @param @return   
    * @return CopyAble    
    * @throws
     */
    public CopyAble runCopy(CopyAble source) {
        return source.copy();
    }

}
package prototype;

/**
 * 客户端程序  使用复印机,对现有的可复制资料进行复制
* @ClassName: Client 
* @author beteman6988
* @date 2017年10月23日 下午11:37:18 
*
 */
public class Client {
    
    public static void main(String[] args) {
        
         CopyMachine machine=new CopyMachine();
        
         Paper aPaper=new Paper();
         aPaper.setHeader("文件头内空");
         aPaper.setContent("文件内容");
         aPaper.setFooter("文件尾内容");
         
         Paper anotherPaper= (Paper) machine.runCopy(aPaper); //复印机复印出另一文件
         System.out.println(anotherPaper.getHeader());
         System.out.println(anotherPaper.getContent());
         System.out.println(anotherPaper.getFooter());
   

      System.out.println(aPaper==anotherPaper);
      System.out.println(aPaper.getClass()==anotherPaper.getClass());


         
    }
    
}

运行结果如下:

文件头内空
文件内容
文件尾内容
false
true

  

五、模式的约束

  对于第三方客户提供的实现类,这种类往往无权进行修改,这时如何实现对该类实例的克隆,是相当麻烦,这时可以借助一些通用性比较好的工具类来完成,如apache 的org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig)

 

  对于克隆出的新对象和原对象之间需满足以下条件约束

  1 对于任何对象  x.clone()!=x  克隆对像和原对象不是同一个对像  ,该条件必须满足

  2. x.clone().getClass()=x.getClass() ,克隆对像和原对像的类型必须相同

  3.x.clone().equals(x)  , 可选

  

六、模式的变体与扩展

   在实际的开发中,对于需要单独实现克隆的情况相对较少,经常使用的是一些写好的通用工具类,如apache 的org.apache.commons.beanutils.BeanUtils.copyProperties(Object dest, Object orig) ,其实现原理是通过java的反射机制来实现的,拿来就可以直接使用。

七、与其它模式的关系

  如抽像工厂模式或工厂方法模式,这些模式关注点是要产生什么样的对像,对于如何产生这样的对像,适当的情况下就可以结合原型模式,如通过已有的对像产生想要的对像。

八、模式优缺点

   优点:对客户隐藏具体的实现类型,原型模式的客户端只知道原型接口的类型,并不知道具体的实现类型,从而减少了客户端对这些具体实现类的依赖。

         缺点:在实现深度克隆时比较麻烦,对于对像里面有引用,引用里面可能还有引用,每个引用层级的类都必须正确实现clone()方法才能让所有层级的对像正确的实现克隆。

posted on 2017-10-25 23:33  bateman6988  阅读(265)  评论(0编辑  收藏  举报