创建型设计模式 之 深拷贝的原型模式

1 定义

原型模式(Prototype Pattern)属于创建型设计模式之一,它主要用于当直接创建对象的代价较大时对已有对象进行克隆从而节约性能成本。例如一个对象需要高代价的IO或访问数据库之后才能被创建出来,像这情况类初始化需要消化非常多的资源,那么我们一般可以将已经创建出来的对象作为原型进行缓存,在下一次再需要同样对象时对其进行克隆,克隆过程中原类的构造方法不会被执行。原型模式很少单独出现,一般是跟工厂模式进行配合,先将原型对象进行初始化缓存,然后通过工厂提供给调用者。

2 实现

我们沿用工厂模式时的例子来讲原型模式,假设有一个手机厂商要生产智能手机和智能手表两款硬件产品, 其中手机和手表在创建对象时初始化成本很高,所以换成了原型模式的形式来克隆已经存在的原型对象。

产品类,手机和手表都属于硬件,将它们抽象出来,硬件抽象类需要继承Cloneable并重写clone方法:

public abstract class Hardware implements Cloneable {
    protected int mId;
    protected String mName;
    @Override
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
    public abstract void showInfo();
}
public class Phone extends Hardware {
    public Phone(int id, String name) {
        // 假设这里初始化成本很高
        mId = id;
        mName = name;
    }
    @Override
    public void showInfo() {
        System.out.println("手机{" + mId + "," + mName + "}性能很强悍!");
    }
}
public class Watch extends Hardware {
    public Watch(int id, String name) {
        // 假设这里初始化成本很高
        mId = id;
        mName = name;
    }
    @Override
    public void showInfo() {
        System.out.println("手表{" + mId + "," + mName + "}时尚时尚最时尚!");
    }
}

产品工厂(产品缓存),用于提前初始化原型进行缓存,要生成新的对象通过调用缓存对象的clone方法进行克隆出新对象:

public class HardwareFactory {
    private static Map<String, Hardware> sHardwareMap = new HashMap<>();

    public static Hardware createHardware(String hardwareName) {
        Hardware hardware = sHardwareMap.get(hardwareName);
        if (hardware == null) {
            throw new UnsupportedOperationException("不支持该操作");
        }
        return (Hardware) hardware.clone();
    }

    public static void init() {
        Hardware phone = new Phone(1, "手机1号");
        sHardwareMap.put("手机", phone);
        Hardware watch = new Watch(2, "手表2号");
        sHardwareMap.put("手表", watch);
    }
}

调用者:先对硬件工厂初始化生成产品对象原型,往后调用createHardware方法直接获取克隆后的新对象:

HardwareFactory.init();

Hardware phone = HardwareFactory.createHardware("手机");
phone.showInfo();
Hardware phone2 = HardwareFactory.createHardware("手机");
phone2.showInfo();

Hardware watch = HardwareFactory.createHardware("手表");
watch.showInfo();
Hardware watch2 = HardwareFactory.createHardware("手表");
watch2.showInfo();

运行输出

手机{1,手机1号}性能很强悍!
手机{1,手机1号}性能很强悍!
手表{2,手表2号}时尚时尚最时尚!
手表{2,手表2号}时尚时尚最时尚!

3 深拷贝和浅拷贝

我们来理解两个概念,浅拷贝和深拷贝。浅拷贝就是拷贝对象的内存地址,例如代码:objA = objB;,其中这两个obj无论哪一个对象里的属性发生变化都会影响到另一个对象,因为这个赋值过程是一个浅拷贝,只拷贝了内存地址,就是它们实际上还是指向同一个对象。而深拷贝是创建出一个全新的对象,新旧两个对象不会因为其中一方对象的改变的发生影响。

那么我们在上述实现的示例中是浅拷贝还是深拷贝?答案是浅拷贝。我们下面就来改进上述产品类来举出一个反例:

产品类:本次新在硬件抽象类中将意新增了一个Map和自定义Cpu类:

public abstract class Hardware implements Cloneable {
    protected int mId;
    protected String mName;
    protected Map<String, Integer> mScore;
    protected Cpu mCpu;

    public void setId(int id) {
        mId = id;
    }
    public int getId() {
        return mId;
    }
    public void setName(String name) {
        mName = name;
    }
    public void setScore(Map<String, Integer> score) {
        mScore = score;
    }
    public void setCpuType(String type) {
        mCpu.setType(type);
    }
    @Override
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
    public abstract void showInfo();
}
public class Phone extends Hardware {
    public Phone(int id, String name, Map<String, Integer> score, Cpu cpu) {
        mId = id;
        mName = name;
        mScore = score;
        mCpu = cpu;
    }
    @Override
    public void showInfo() {
        System.out.println("手机{" + mId + "," + mName + "}性能很强悍, CPU:" + mCpu.getType() + ", 跑分:" + mScore);
    }
}
public class Cpu {
    private String mType;
    public Cpu(String type) {
        mType = type;
    }
    public void setType(String type) {
        this.mType = type;
    }
    public String getType() {
        return mType;
    }
}

调用者

Map<String, Integer> score = new HashMap<>();
score.put("屏幕跑分", 999);
score.put("内存跑分", 888);
Map<String, Integer> score2 = new HashMap<>();
score2.put("屏幕跑分", 100);
score2.put("内存跑分", 200);

Phone phone1 = new Phone(1, "手机1号", score, new Cpu("骁龙865"));
phone1.showInfo();

Phone phone2 = (Phone)phone1.clone();

phone1.setId(2);
phone1.setName("手机2号");
phone1.setScore(score2);
phone1.setCpuType("骁龙770");

phone2.showInfo();

运行输出

手机{1,手机1号}性能很强悍, CPU:骁龙865, 跑分:{屏幕跑分=999, 内存跑分=888}
手机{1,手机1号}性能很强悍, CPU:骁龙770, 跑分:{屏幕跑分=999, 内存跑分=888}

解说:

为了演示,我们本次省略了产品工厂(产品缓存)的角色,直接让调用者来克隆产品对象。调用者中先创建出一个phone1对象作为原型,接着克隆出phone2对象,然后对原型phone1进行4种不同类型的属性的修改,分别是:int、String、Map和自定义的CPU。从输出结可见,除了CPU类被修改,其余属性没有因原型修改而被修改。

原因其实是因为Cloneable的clone方法提供一种是浅拷贝,而且这个拷贝是有选择性,比如基本类型如:int、float、double等都是拷贝其值,而String、Integer、Float、Double等和集合它们虽然是引用类型,但比较特殊它们也是拷贝其值的,而自定义的类就不行了,它只拷贝了地址,所以在phone1后面修改了后,随着也会影响到phone2的值。

3.1 实现深拷贝改进

使产品类里所引用到的类也继承于Cloneable,并实现clone方法:

public class Cpu implements Cloneable {
    private String mType;
    public Cpu(String type) {
        mType = type;
    }
    public void setType(String type) {
        this.mType = type;
    }
    public String getType() {
        return mType;
    }
    @Override
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
}

对产品抽象类中clone方法进行添加对类内其它自定义类的克隆,这里添加了代码:mCpu = (Cpu)mCpu.clone();

public abstract class Hardware implements Cloneable {
    protected int mId;
    protected String mName;
    protected Map<String, Integer> mScore;
    protected Cpu mCpu;
    public void setId(int id) {
        mId = id;
    }
    public int getId() {
        return mId;
    }
    public void setName(String name) {
        mName = name;
    }
    public void setScore(Map<String, Integer> score) {
        mScore = score;
    }
    public void setCpuType(String type) {
        mCpu.setType(type);
    }
    @Override
    public Object clone() {
        Object clone = null;
        try {
            clone = super.clone();
            mCpu = (Cpu)mCpu.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;
    }
    public abstract void showInfo();
}

经修改后运行输出

手机{1,手机1号}性能很强悍, CPU:骁龙865, 跑分:{屏幕跑分=999, 内存跑分=888}
手机{1,手机1号}性能很强悍, CPU:骁龙865, 跑分:{屏幕跑分=999, 内存跑分=888}

3.2 使用Serializable实现深拷贝

除了上面所述让产口类中所引用的所有类也继承Cloneable实现clone来实现深拷贝外,还可以使用serializable接口通过读取二进制流序列化的方式来实现对象的深复制。

使产品类里所引用到的类直接继承Serializable即可:

public class Cpu implements Serializable {
    private String mType;
    public Cpu(String type) {
        mType = type;
    }
    public void setType(String type) {
        this.mType = type;
    }
    public String getType() {
        return mType;
    }
}

对产品抽象类也经继承Serializable,并修改其clone方法,代码如:

public abstract class Hardware implements Cloneable, Serializable {
    protected int mId;
    protected String mName;
    protected Map<String, Integer> mScore;
    protected Cpu mCpu;
    public void setId(int id) {
        mId = id;
    }
    public int getId() {
        return mId;
    }
    public void setName(String name) {
        mName = name;
    }
    public void setScore(Map<String, Integer> score) {
        mScore = score;
    }
    public void setCpuType(String type) {
        mCpu.setType(type);
    }
    @Override
    public Object clone() {
//        Object clone = null;
//        try {
//            clone = super.clone();
//            mCpu = (Cpu)mCpu.clone();
//        } catch (CloneNotSupportedException e) {
//            e.printStackTrace();
//        }
//        return clone;
        Object clone = null;
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(this);

            ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);

            clone = objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return clone;
    }
    public abstract void showInfo();
}

经修改后运行输出

手机{1,手机1号}性能很强悍, CPU:骁龙865, 跑分:{屏幕跑分=999, 内存跑分=888}
手机{1,手机1号}性能很强悍, CPU:骁龙865, 跑分:{屏幕跑分=999, 内存跑分=888}

4 总结

原型模式实现原理很简单。因为它是为了性能优化而生,所以它的使用场景也很明确就是当初始化类的成本比缓存已有对象原型的成本更高时建议使用原型模式。使用上要注意就是克隆对象时,原类的构造方法是不会被执行的,还有就是考虑类中字段是否满足深拷贝,如果不满足就要对其进行支持改进,支持方式可以是让引用到的类也继承Cloneable接口并实现clone方法或者使用serializable接口通过读取二进制流序列化的方式来实现对象的深复制。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章