设计模式之原型模式(八)

原型模式

简介

         原型模式其实就是通过实例创建另一个对象,而且不需要知道任何的创建细节。

Cloneable接口

         cloneable接口没有任何方法,如果一个类实现了cloneable接口,那么Object.clone()方法就可以合法的对该类实例进行字段的负责。如果一个类没有实现cloneable接口,调用Object.clone()方法,那么就会抛出CloneNotSupportedException异常。

         原型模式就是基于cloneable接口来创建对象的。虽然cloneable接口没有任何方法,但是他的作用声明了此类是可以调用Object.clone()方法复制对象的。

 

实现方式

         现在的社会中信用卡可以说是银行推出的比较受消费者欢迎的一种产品,因为可以使用未来的钱。但是银行每个月底都会通知你下个月需要还款金额,下面就是基于银行的短信通知的实现。

普通方式

         银行的短信通知一定是通过模板,套入用户的信息批量发送的,不可能说工作人员一个一个的发送,那么这样就可以模拟普通的方式去发送短信。

 

package com.rabbit.pattern.prototype.test;

/**
 * 通知模板
 * Created by vip on 2018/3/7.
 */
public class Inform {

    private String context = "尊敬的{name}先生(女生),您好!您本月信用卡消费{amount}元,请按时还款!";

    private String name;

    private double amount;

    public Inform(String name, double amount) {
        System.out.println("Inform构造函数被调用了......");
        this.name = name;
        this.amount = amount;
    }

    public void sendMsg() {
        System.out.println(context.replace("{name}", name)
                .replace("{amount}", String.valueOf(amount)));
    }
}

package com.rabbit.pattern.prototype.test;

/**
 * Created by vip on 2018/3/8.
 */
public class Demo {

    public static void main(String[] args) {
        for (int i = 1; i <= 5; i++) {//模拟给多个用户发信息
            new Inform(i + "号", 50 * i).sendMsg();
        }
    }

}

这种普通的调用方式每次都需要通过new的方式去创建对象,并且没次都调用了构造函数。如果通过new的方式创建对象需要比较大的开销,如果银行要给1000W的用户发送还款通知,那么需要多少的服务器资源才可以处理完毕呢?而且有可能导致内存溢出服务器崩溃。

         简单的介绍new的方式去创建对象好比我们找工作时候,需要打印简历,但是每次的简历都是需要做小小的修改的。那么new的方式就是每次都重新写一份新的简历,而不是在之前的简历上面去做修改。原型模式的原理就不是每次去写一份新的简历,而是复制一份简历模板去修改,由于是复制一份,所有即使修改了对之前的也是没有任何影响的。

原型模式方式

         Cloneable接口中介绍了cloneable接口的基本信息,那么原型模式就是基于这个接口去实现的,如果一个类没有实现cloneable接口,那么是不可以使用原型模式,即使使用了原型模式,在复制的时候也会出现异常(就好比一个windows系统中的一个文件已经设置为不可以删除,那么你强制要删除,就会出现系统提示,如果要删除就要设置为可以删除系统才不会提示你)。所以,一个类要基于原型模式首先要实现Cloneable接口,但是这个接口是没有任何方法需要实现的,只是做个声明而已。

package com.rabbit.pattern.prototype.test;

/**
 * 通知模板
 * Created by vip on 2018/3/7.
 */
public class Inform implements Cloneable {

    private String context = "尊敬的{name}先生(女生),您好!您本月信用卡消费{amount}元,请按时还款!";

    private String name;

    private double amount;

    public Inform(String name, double amount) {
        System.out.println("Inform构造函数被调用了......");
        this.name = name;
        this.amount = amount;
    }

    public void sendMsg() {
        System.out.println(context.replace("{name}", name)
                .replace("{amount}", String.valueOf(amount)));
    }

    @Override
    protected Inform clone() throws CloneNotSupportedException {
        return (Inform)super.clone();
    }

    public String getName() {
        return name;
    }

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

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }
}
这个类实现了Cloneable接口,并且重写了Object.clone()方法,这个方法就是原型模式的重点,通过这个方法去实现对象的复制。
package com.rabbit.pattern.prototype.test;

/**
 * Created by vip on 2018/3/8.
 */
public class Demo {

    public static void main(String[] args) throws CloneNotSupportedException {
        Inform i1 = new Inform("张三", 400);
        Inform i2 = i1.clone();
        i2.setName("李四");
        i2.setAmount(900);

        Inform i3 = i1.clone();
        i3.setName("王五");
        i3.setAmount(1900);

        i1.sendMsg();
        i2.sendMsg();
        i3.sendMsg();
    }

}

基于原型模式实现后,在结果中可以看到构造函数只是被调用了一次,i2和i3实例是通过调用i1的Clone方法复制的到的。那么既然复制了一份出来,那么即使去修改i2和i3实例的属性,它们之间也不影响,因为大家都有一份独立的,你改你的,我改我的。

 

存在的问题

         上面的基于原型模式一看没有任何问题,但是下面的代码就出问题了,我们添加用户的消费清单的一个属性,用ArrayList集合表示用户上个月的消费清单。

package com.rabbit.pattern.prototype.test;

import java.util.ArrayList;

/**
 * 通知模板
 * Created by vip on 2018/3/7.
 */
public class Inform implements Cloneable {

    private String context = "尊敬的{name}先生(女生),您好!您本月信用卡消费{amount}元,请按时还款!";

    private String name;

    private double amount;

    private ArrayList<Double> consume;//消费清单

    public Inform(String name, double amount, ArrayList<Double> consume) {
        System.out.println("Inform构造函数被调用了......");
        this.name = name;
        this.amount = amount;
        this.consume = consume;
    }

    public void sendMsg() {
        System.out.println(context.replace("{name}", name)
                .replace("{amount}", String.valueOf(amount)));
        System.out.println("消费清单如下:" + consume);
    }

    @Override
    protected Inform clone() throws CloneNotSupportedException {
        Inform clone = (Inform) super.clone();
        return clone;
    }

    public String getName() {
        return name;
    }

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

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public ArrayList<Double> getConsume() {
        return consume;
    }

    public void setConsume(ArrayList<Double> consume) {
        this.consume = consume;
    }
}

package com.rabbit.pattern.prototype.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by vip on 2018/3/8.
 */
public class Demo {

    public static void main(String[] args) throws CloneNotSupportedException {
        ArrayList<Double> consume = new ArrayList<>();
        createConsume(consume);
        Inform i1 = new Inform("张三", 400, consume);

        Inform i2 = i1.clone();
        i2.setName("李四");
        i2.setAmount(900);
        createConsume(i2.getConsume());

        Inform i3 = i1.clone();
        i3.setName("王五");
        i3.setAmount(1900);
        createConsume(i3.getConsume());

        i1.sendMsg();
        i2.sendMsg();
        i3.sendMsg();
    }

    public static void createConsume(List<Double> consume) {
        Random r = new Random();
        for (int i = 1; i <= 2; i++) {
            consume.add(Double.valueOf(r.nextInt(100) * i));
        }
    }

}

发现什么问题没有,大家的消费清单都是一样的,如果这样的消费清单给用户,那么会出现什么问题,估计银行的客服电话要被打爆了,我没消费那么多,为什么出现了这样的清单呢?

         那么归根结底为什么会出现这样的情况呢?明明大家都是复制了一份,你改你的,我改我的,相互之间应该没有影响才对啊!不要着急,下面我给大家介绍一下为什么会出现这个情况。

浅复制和深复制

         浅复制,被复制对象的所有基本变量都含有和原来对象相同的值,而且所有的引用对象仍然指向原来的对象。

         深复制,在浅复制的基础上,所有引用对象指向复制过来的新对象,而不是指向原有的引用对象。

         说到这里大概就明白了为什么大家的消费清单都是一样的,因为保存清单的ArrayList是一个引用类型的变量,在复制的时候还是指向之前的地址,所有大家修改都是修改了同一份。那么怎么处理这种情况呢?这样的复制叫浅复制,要解决浅复制问题就要使用深复制了。

解决浅复制问题

package com.rabbit.pattern.prototype.test;

import java.util.ArrayList;

/**
 * 通知模板
 * Created by vip on 2018/3/7.
 */
public class Inform implements Cloneable {

    private String context = "尊敬的{name}先生(女生),您好!您本月信用卡消费{amount}元,请按时还款!";

    private String name;

    private double amount;

    private ArrayList<Double> consume;//消费清单

    public Inform(String name, double amount, ArrayList<Double> consume) {
        System.out.println("Inform构造函数被调用了......");
        this.name = name;
        this.amount = amount;
        this.consume = consume;
    }

    public void sendMsg() {
        System.out.println(context.replace("{name}", name)
                .replace("{amount}", String.valueOf(amount)));
        System.out.println("消费清单如下:" + consume);
    }

    @Override
    protected Inform clone() throws CloneNotSupportedException {
        Inform clone = (Inform) super.clone();
        clone.setConsume((ArrayList<Double>)consume.clone());
        return clone;
    }

    public String getName() {
        return name;
    }

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

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public ArrayList<Double> getConsume() {
        return consume;
    }

    public void setConsume(ArrayList<Double> consume) {
        this.consume = consume;
    }
}

注意看clone方法,深复制其实就是对于引用类型的遍历重新复制一份给当前的实例,这样大家也有一份了,修改就互不影响了。那么这里还有个问题,就是深复制要深入到多少层呢?这里深复制深入到了一层,如果ArrayList(ArrayList实现了Cloneable接口)内部还需要复制其他的引用类型变量,就需要继续深复制了,所有具体深入到多层需要看你的类设计的是不是很复杂。

package com.rabbit.pattern.prototype.test;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * Created by vip on 2018/3/8.
 */
public class Demo {

    public static void main(String[] args) throws CloneNotSupportedException {
        ArrayList<Double> consume = new ArrayList<>();
        createConsume(consume);
        Inform i1 = new Inform("张三", 400, consume);

        Inform i2 = i1.clone();
        i2.setName("李四");
        i2.setAmount(900);
        i2.getConsume().clear();
        createConsume(i2.getConsume());

        Inform i3 = i1.clone();
        i3.setName("王五");
        i3.setAmount(1900);
        i3.getConsume().clear();
        createConsume(i3.getConsume());

        i1.sendMsg();
        i2.sendMsg();
        i3.sendMsg();
    }

    public static void createConsume(List<Double> consume) {
        Random r = new Random();
        for (int i = 1; i <= 2; i++) {
            consume.add(Double.valueOf(r.nextInt(100) * i));
        }
    }
    
}

通过深复制后,大家的账单就不一样了,都是自己的。终于银行客服的电话不被消费者打爆了,开发人员也不用通宵加班改bug了。

 

总结

优点

1)提高系统性能,节约资源(简历的重新抄和拷贝再修改一样的道理)。

2)躲避了构造函数的限制,因为原型模式不用通过构造函数创建实例。

3)隐藏了对象的创建细节。只知道实例拷贝,不知道具体拷贝的是那个对象。

4)动态的获取实例,需要在那个实例上修改,就拷贝那个实例即可。

缺点

1)必须实现Cloneable接口。


展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie
应支付0元
点击重新获取
扫码支付

支付成功即可阅读