设计模式从入门到放弃(四)原型模式

原型模式 Prototype

  • 原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  • Java中Object类中提供了clone方法,可以用于实现原型模式,但使用clone方法必须实现Cloneable接口,表示该类可以被克隆

核心角色

  • Prototype(原型类)被克隆的对象 顶级接口
  • ConcretePrototype(具体原型类): 实现接口,实现克隆方法
  • Client(调用者)

UML 示意图

在这里插入图片描述

代码实现

// 实现Cloneable接口 最简单的原型实现 这里不能直接用Lombok@Data 因为@Data重写了hashCode方法导致Clone出来的hashCode一样
@Setter
@Getter
@ToString
public class PrototypeClass implements Cloneable {
    private String name;

    private InnerClass innerClass;

    @Override
    protected Object clone() {
        try {
            PrototypeClass clone = (PrototypeClass) super.clone();
//            clone.innerClass = (InnerClass) innerClass.clone();
            return clone;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return new PrototypeClass();
    }
}

// 内部引用类型属性
@Setter
@Getter
@ToString
public class InnerClass implements Cloneable {

    private String innerName;

    @Override
    protected Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return new InnerClass();
    }
}
// 客户端调用  发现他们的属性 和hashcode 都是一样的
public class PrototypeTest {

    public static void main(String[] args) {
        PrototypeClass prototypeClass1 = new PrototypeClass();
        prototypeClass1.setName("test");
        InnerClass innerClass = new InnerClass();
        innerClass.setInnerName("innerClass");
        prototypeClass1.setInnerClass(innerClass);
        System.out.println(prototypeClass1.toString() + prototypeClass1.getInnerClass().hashCode());

        PrototypeClass prototypeClass2 = (PrototypeClass) prototypeClass1.clone();
        System.out.println(prototypeClass2.toString() + prototypeClass2.getInnerClass().hashCode());

        PrototypeClass prototypeClass3 = (PrototypeClass) prototypeClass1.clone();
        System.out.println(prototypeClass3.toString() + prototypeClass3.getInnerClass().hashCode());

        PrototypeClass prototypeClass4 = (PrototypeClass) prototypeClass1.clone();
        System.out.println(prototypeClass4.toString() + prototypeClass4.getInnerClass().hashCode());
    }
}
//PrototypeClass(name=test, innerClass=InnerClass(innerName=innerClass))1836019240
//PrototypeClass(name=test, innerClass=InnerClass(innerName=innerClass))1836019240
//PrototypeClass(name=test, innerClass=InnerClass(innerName=innerClass))1836019240
//PrototypeClass(name=test, innerClass=InnerClass(innerName=innerClass))1836019240


以上是最简单实现原型模式克隆的最简单的方法,存在的问题是浅拷贝,无法真正拷贝为对象的属性。

简单介绍一下深浅拷贝的问题

  • 浅拷贝

    对于基本数据类型的成员变量,浅拷贝直接是值传递,将值复制一份给新对象

    对于引用类型的成员变量,比如数组、对象等,浅拷贝可以理解为直接拷贝属性内存地址(引用)实际上还是指向的同一个对象,这样的情况下如果任意一个修改了引用对象里面的属性,其他所有克隆出现的对象都被改变 Object的clone方法就是用的浅拷贝方式

  • 深拷贝

    复制对象的所有基本类型的成员变量

    对于引用类型重新申请一块内存空间来专门存储新的对象,对引用类型内部的成员变量用一样的方法进行。

    实现方式主要有两种 重写clone方法 或者 使用对象序列化反序列来实现(推荐使用)

// 模拟引用类型实现Cloneable接口
public class InnerClass implements Cloneable {

    private String innerName;

    @Override
    protected Object clone(){
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return new InnerClass();
    }
}

// 含有引用类型的类
public class PrototypeClass implements Cloneable {
    private String name;

    private InnerClass innerClass;

    @Override
    protected Object clone() {
        try {
            PrototypeClass clone = (PrototypeClass) super.clone();
            // 重新调用引用类型的克隆方法进行赋值
            clone.innerClass = (InnerClass) innerClass.clone();
            return clone;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return new PrototypeClass();
    }
}

上面这种方法实现简单只是把对象的innerClass属性再次clone,但缺点也很明显,如果对象有多个引用属性,每一个都要去重写方法很麻烦。下面使用序列化和反序列化实现虽然代码麻烦一点,但是使用简单

@Setter
@Getter
@ToString
public class PrototypeClass implements Cloneable,Serializable {
    private String name;

    private InnerClass innerClass;

    @Override
    protected Object clone() {
        ByteArrayOutputStream bos = null;
        ByteArrayInputStream bis = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            // 写出去
            oos.writeObject(this);
            // 重新写回来
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            return ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

  1. 首先所有类包括引用成员类型的变量都实现序列化接口
  2. 将对象序列化写出到流,然后用输入流读回来返回

猜你喜欢

转载自blog.csdn.net/woshiwjma956/article/details/105793941