设计模式之原型模式

原型模式是一种创建型设计模式,它通过复制一个已经存在的实例来返回新的实例,而不是新建实例.被复制的实例就是我们所称的原型.

意图

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

别名

clone

结构与参与者

这里写图片描述
Prototype(原型类):声明一个克隆自身的接口
ConcretePrototype(具体原型类):实现一个克隆自身的操作
Client(调用类):让一个原型克隆自身从而创建一个新的对象


原型模式的核心是clone()方法,通过该方法进行对象的拷贝。
Java提供了一个Cloneable接口来表示这个对象时可拷贝的。
从“有可能被拷贝”到“可以被拷贝”
需要重写clone()方法。


举个栗子:
投简历
需求:找工作要向三个公司投简历
要求名字,性别等基本信息

简历类

public class Resume {
    private String name;
    private String age;
    private String gender;
    private String education;

    public Resume(String name,String age,String gender,String education){
        this.name=name;
        this.age=age;
        this.gender=gender;
        this.education=education;
    }
    public void show(){
        System.out.println(name+" "+" "+age+" "+gender+" "+" "+education);
    }

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getEducation() {
        return education;
    }

    public void setEducation(String education) {
        this.education = education;
    }
}
public class Client {
    @Test
    public void fun1() {
        Resume resume1=new Resume("张三", "18", "男", "本科");
        Resume resume2=new Resume("张三", "18", "男", "本科");
        Resume resume3=new Resume("张三", "18", "男", "本科");
        resume1.show();
        resume2.show();
        resume3.show();
    }
}

运行结果

张三  18 男  本科
张三  18 男  本科
张三  18 男  本科

问题及改进
在每次需要新简历的时候,都要使用
Resume resume=new Resume(“张三”, “18”, “男”, “本科”);
进行初始化对象。
三份简历需要三次实例化,如果是二十份、五十份,就需要二十次、五十次的实例化,显然对客户端的压力会增大。

  @Test
    public void fun2(){
        Resume resume1=new Resume("张三", "18", "男", "本科");
        Resume resume2=resume1;
        Resume resume3=resume1;
        resume1.show();
        resume2.show();
        resume3.show();
    }

虽然运行结果一致,但这么做却没有实际意义。
传的是引用,而不是传值。


使用原型模式改进简历代码

    @Test
    public void fun2(){
        Resume resume1=new Resume("张三", "18", "男", "本科");
        Resume resume2=resume1;
        Resume resume3=resume1;
        resume1.show();
        resume2.show();
        resume3.show();
    }

运行结果

张三  18 男  本科
张三  18 男  本科
张三  18 男  本科

浅复制


被拷贝对象的所有变量都含有与原对象相同的值,而且对其他对象的引用仍然是指向原来的对象。即浅拷贝只负责当前对象实例,对引用的对象不做拷贝。
这里写图片描述
java八大基本类型
int、float、double、char、byte、long、boolean、short
属于值类型变量。
在clone()中,将String 视为值类型变量。
其他类型如 ArrayList等都属于引用变量


深复制


被拷贝对象的所有变量都含有与原来对象相同的值,除了那些引用其他对象的变量。那些引用其他对象的变量将指向一个被拷贝的新对象,而不再是原有那些被引用对象。
这里写图片描述

示例说明

投简历
需求:找工作要向三个公司投简历
要求名字,性别等基本信息
必须含有工作经历。

//工作经历
public class WorkExperience implements Cloneable{

    private String workDate;
    private String company;

    public String getWorkDate() {
        return workDate;
    }

    public void setWorkDate(String workDate) {
        this.workDate = workDate;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }
    @Override
    public WorkExperience clone(){
        WorkExperience workExperience=null;
        try{
            workExperience=(WorkExperience)super.clone();
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
        return workExperience;
    }

}
//简历
public class Resume implements Cloneable{
    private String name;
    private String age;
    private String gender;
    private String education;
    private WorkExperience work;
    public Resume(String name,String age,String gender,String education){
        this.name=name;
        this.age=age;
        this.gender=gender;
        this.education=education;
        work=new WorkExperience();
    }
    public void show(){
        System.out.println(name+" "+" "+age+" "+gender+" "+" "+education);
        System.out.println("工作经历:"+work.getWorkDate()+"   "+work.getCompany());
    }
    public void SetWorkExperience(String workDate,String company){
        work.setWorkDate(workDate);
        work.setCompany(company);
    }
  /*
  //浅复制
  @Override
    public Resume clone(){
        Resume resume=null;
        try{
            resume=(Resume)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
        return resume;
    }*/
  //深复制
  @Override
  public Resume clone(){
      Resume resume=null;
      try{
          resume=(Resume)super.clone();
          resume.work=this.work.clone();
      }catch(CloneNotSupportedException e){
          e.printStackTrace();
      }
      return resume;
  }
}
public class Client {
    @Test
    public void fun1(){
        Resume resume1=new Resume("张三", "18", "男", "本科");
        resume1.SetWorkExperience("2002-2012","xx公司");
        Resume resume2= resume1.clone();
        resume2.SetWorkExperience("2003-2014","yy公司");
        Resume resume3= resume1.clone();
        resume3.SetWorkExperience("2004-2016","zz公司");
        resume1.show();
        resume2.show();
        resume3.show();

    }
}

运行结果:

张三  18 男  本科
工作经历:2004-2016   zz公司
张三  18 男  本科
工作经历:2004-2016   zz公司
张三  18 男  本科
工作经历:2004-2016   zz公司

广告邮件发送
这里写图片描述

public class AdvTemplate {
    private String subject="XX银行XXX活动";
    private String context="只要刷卡就送100万";

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getContext() {
        return context;
    }

    public void setContext(String context) {
        this.context = context;
    }
}
public class Mail implements Cloneable {
    private String receiver;//接收者
    private String subject;//主题
    private String appellation;//称谓
    private String context;//正文
    private String tail;//结尾

    public Mail(AdvTemplate advTemplate) {
        this.subject=advTemplate.getSubject();
        this.context=advTemplate.getContext();
    }
    @Override
    protected Mail clone(){
        Mail mail=null;
        try {
            mail=(Mail)super.clone();
        }catch (Exception e){
            e.printStackTrace();
        }
        return mail;
    }
    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getAppellation() {
        return appellation;
    }

    public void setAppellation(String appellation) {
        this.appellation = appellation;
    }

    public String getContext() {
        return context;
    }

    public void setContext(String context) {
        this.context = context;
    }

    public String getTail() {
        return tail;
    }

    public void setTail(String tail) {
        this.tail = tail;
    }
}

import java.util.Random;

/**
 * Created by yangy on 2017/6/21.
 */
public class Client {
    private static final int MAX_COUNT=6;
    public static void main(String[] args) {
        int i=0;
        Mail mail=new Mail(new AdvTemplate());
        mail.setTail("XXX_copyright");
        while (i<MAX_COUNT){
            mail.setAppellation(getRandString(5)+"先生(女士)");
            mail.setReceiver(getRandString(5)+"@"+getRandString(8)+".com");
            sendMail(mail);
            i++;
        }
    }
    public static void sendMail(Mail mail){
        System.out.println("标题:"+mail.getSubject()+"\t收件人:"+mail.getReceiver()+"\t 发送成功");
        System.out.println("内容为:尊敬的"+mail.getAppellation()+","+mail.getContext());
    }
    public static String getRandString(int maxLength){
        String source="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuffer stringBuffer=new StringBuffer();
        Random random=new Random();
        for (int i=0;i<maxLength;i++){
            stringBuffer.append(source.charAt(source.length()-1));
        }
        return stringBuffer.toString();
    }
}

运行结果:

标题:XX银行XXX活动    收件人:ZZZZZ@ZZZZZZZZ.com   发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动    收件人:ZZZZZ@ZZZZZZZZ.com   发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动    收件人:ZZZZZ@ZZZZZZZZ.com   发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动    收件人:ZZZZZ@ZZZZZZZZ.com   发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动    收件人:ZZZZZ@ZZZZZZZZ.com   发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100万
标题:XX银行XXX活动    收件人:ZZZZZ@ZZZZZZZZ.com   发送成功
内容为:尊敬的ZZZZZ先生(女士),只要刷卡就送100

原型模式的效果

Prototype有许多和Abstract Factory一样的效果;它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。
优点:
1.如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2.可以使用深克隆保持对象的状态。
3.扩展性好。
4.原型模式提供了简化的创建结构

缺点:
1.在实现深克隆的时候可能需要比较复杂的代码。
2.需要为每一个类配备一个克隆方法,可且这个克隆方法需要对类的功能进行通盘考虑,在修改已有类是违背“开闭原则”。

带有管理器的原型模式

这里写图片描述
示例代码:

public class Prototype implements Cloneable {
    @Override
    protected Prototype clone()throws CloneNotSupportedException{
        return (Prototype) super.clone();
    }
}
public class ConcretePrototype extends Prototype {
    @Override
    protected  ConcretePrototype clone() throws CloneNotSupportedException{
        return (ConcretePrototype)super.clone();
    }
}
public class ConcretePrototype extends Prototype {
    @Override
    protected  ConcretePrototype clone() throws CloneNotSupportedException{
        return (ConcretePrototype)super.clone();
    }
}
public class PrototypeManager {
    private Map<String,Prototype> map=new HashMap<String,Prototype>();
    public boolean contain(String name){
        return map.containsKey(name);
    }
    public void add(String path,String name){
        try {
            //通过类路径来实例化该类
            Prototype prototype=(Prototype) Class.forName(path).newInstance();
            map.put(name,prototype);
            System.out.println("Add"+prototype.getClass());
        }catch (InstantiationException|IllegalAccessException|ClassNotFoundException e){
            e.printStackTrace();
        }
    }
    public Prototype get(String name)throws CloneNotSupportedException{
        System.out.println("Clone a prototype");
        return map.get(name).clone();
    }
}
public class Client {
    static PrototypeManager prototypeManager=new PrototypeManager();
    static  Prototype prototype;

    public static void main(String[] args) {
        String path="ClonePatternDemo04.ConcretePrototype";
        String name="ConcretePrototype";
        if(!prototypeManager.contain(name)){
            prototypeManager.add(path,name);
        }
        //克隆出所需对象
        try {
            prototype=prototypeManager.get(name);
        }catch (CloneNotSupportedException e){
            e.printStackTrace();
        }
    }
}

两种方法的比较

若需要创建的原型对象数目较少并且种类固定,使用普通的原型模型。在这种情况下原型对象的引用由客户端自己保存。
如果创建的原型对象不固定,则使用带有登记管理器的原型模式。这种情况下,客户端不保存原型对象的引用。

相关模式

1、原型模式可以和所有的工厂模式一起使用,原型模式通过clone()方法创建一个对象,再由工厂方法将创建的对象提供给调用者。

2、原型模式也常常和合成模式、装饰模式一起使用。

已知应用

1.音乐编辑器Unidraw绘图框架。
2.ThingLab中,用户能够生成复合对象,然后把它安装到一个可重用的对象库中促使它成为一个原型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值