avatar

java原型模式

克隆羊问题

现在有一只羊tom,姓名:tom,年龄为:1,颜色为:白色,请编写程序创建和tom羊属性完全一样的10只羊。

//创建一个sheep的类
public class Sheep {
private String name;
private int age;
private String color;
public Sheep(String name,int age,String color)
{
this.age = age;
this.color=color;
this.name=name;
}
public int getAge() {
return age;
}
public String getColor() {
return color;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setColor(String color) {
this.color = color;
}
public String getName() {
return name;
}
@Override
public String toString() {
return super.toString();
}
}
//调用
public class Client {
public static void main(String[] args)
{
Sheep sheep = new ("tom",1,"白色");
Sheep sheep1 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());
Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());
//....实例化十个
}
}
  • 优点是比较好理解,简单易操作
  • 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂,效率较低
  • 总是需要重新初始化对象,而不是动态的获取对象运行时的状态不够灵活
    思路:java中Object类时所有类的根类,Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,但是需要实现clone的java类必须要实现一个接口Cloneable,该接口表示该类能够复制且具有复的能力=》原型模式

原型模式

基本介绍

  • 原型模式(Prototype模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节
  • 工作原理:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实施创建,即对象.clone()

原型模式结构图

代码修改

自己实现,抛开java提供的方式,自己去实现一个原型模式

public abstract class Prototype {
private String id;
public Prototype(String id)
{
this.id = id;
}
public String GetId()
{
return this.id;
}
public abstract Prototype Clone();
}
public class ConretePrototype extends Prototype {

public ConretePrototype(String id) {
super(id);
}

@Override
public Prototype Clone() {
//实现克隆方法,这个克隆方法自己去写
return null;
}
}
//客户端调用
public class Client {
public static void main(String[] args)
{
ConretePrototype conretePrototype = new ConretePrototype("1");
ConretePrototype conretePrototype1 = (ConretePrototype)conretePrototype.Clone();
}
}

使用java自带的接口实现原型模式–默认浅拷贝

通过实现接口Cloneable,实现其中的方法clone(),来实现

public class Sheep implements Cloneable{
private String name;
private int age;
private String color;
public Sheep(String name, int age, String color)
{
this.age = age;
this.color=color;
this.name=name;
}
public int getAge() {
return age;
}
public String getColor() {
return color;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setColor(String color) {
this.color = color;
}
public String getName() {
return name;
}
@Override
public String toString() {
return super.toString();
}
//克隆该实例,使用一个默认的clone方法
//这里我们必须重写clone才能使用Object的方法,因为Object的方法是被protected修饰
//protected的作用域是
//1、类的protected成员是包内可见的,并且对子类可见
//2、若子类与父类不在同一包中,那么在子类中,子类实例可以访问其从父类继承而来的protected方法,而不能访问父类实例的protected方法。
//https://blog.csdn.net/asahinokawa/article/details/80777302
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

客户端调用

public class Client {
public static void main(String[] args)
{

try {
Sheep sheep = new Sheep("tom",1,"白色");
Sheep sheep1 = (Sheep) sheep.clone();
System.out.println(sheep.hashCode());
System.out.println(sheep1.hashCode());
}catch (Exception ex)
{
ex.printStackTrace();
}
}
}

这样就实现了一个浅拷贝,浅拷贝就指复制当前对象地址,但是当前对象里面的引用类型还是指向原地址。
这里我有两个疑问,为什么一定要继承Cloneable才可以实现拷贝,java是如何实现这个机制的?为什么要设置这层屏障?

  • 问题一:为什么一定要继承Cloneable才可以实现拷贝,java是如何实现这个机制的
    首先我们需要看一下Object的源码发现Clone方法是这样的

    protected native Object clone() throws CloneNotSupportedException;

    这也就是说java并没有自己去实现clone这一方法,而是一个native方法,所以需要去看看这个实现,他的实现是在虚拟机内实现的,路径为:\hotspot\src\share\vm\prims\jvm.cpp

    JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
    JVMWrapper("JVM_Clone");
    Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
    const KlassHandle klass (THREAD, obj->klass());
    JvmtiVMObjectAllocEventCollector oam;

    #ifdef ASSERT
    // Just checking that the cloneable flag is set correct
    if (obj->is_array()) {
    guarantee(klass->is_cloneable(), "all arrays are cloneable");
    } else {
    guarantee(obj->is_instance(), "should be instanceOop");
    bool cloneable = klass->is_subtype_of(SystemDictionary::Cloneable_klass());
    guarantee(cloneable == klass->is_cloneable(), "incorrect cloneable flag");
    }
    #endif

    // Check if class of obj supports the Cloneable interface.
    // All arrays are considered to be cloneable (See JLS 20.1.5)
    if (!klass->is_cloneable()) {
    ResourceMark rm(THREAD);
    THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
    }

    // Make shallow object copy
    const int size = obj->size();
    oop new_obj = NULL;
    if (obj->is_array()) {
    const int length = ((arrayOop)obj())->length();
    new_obj = CollectedHeap::array_allocate(klass, size, length, CHECK_NULL);
    } else {
    new_obj = CollectedHeap::obj_allocate(klass, size, CHECK_NULL);
    }
    // 4839641 (4840070): We must do an oop-atomic copy, because if another thread
    // is modifying a reference field in the clonee, a non-oop-atomic copy might
    // be suspended in the middle of copying the pointer and end up with parts
    // of two different pointers in the field. Subsequent dereferences will crash.
    // 4846409: an oop-copy of objects with long or double fields or arrays of same
    // won't copy the longs/doubles atomically in 32-bit vm's, so we copy jlongs instead
    // of oops. We know objects are aligned on a minimum of an jlong boundary.
    // The same is true of StubRoutines::object_copy and the various oop_copy
    // variants, and of the code generated by the inline_native_clone intrinsic.
    assert(MinObjAlignmentInBytes >= BytesPerLong, "objects misaligned");
    Copy::conjoint_jlongs_atomic((jlong*)obj(), (jlong*)new_obj,
    (size_t)align_object_size(size) / HeapWordsPerLong);
    // Clear the header
    new_obj->init_mark();

    // Store check (mark entire object and let gc sort it out)
    BarrierSet* bs = Universe::heap()->barrier_set();
    assert(bs->has_write_region_opt(), "Barrier set does not have write_region");
    bs->write_region(MemRegion((HeapWord*)new_obj, size));

    // Caution: this involves a java upcall, so the clone should be
    // "gc-robust" by this stage.
    if (klass->has_finalizer()) {
    assert(obj->is_instance(), "should be instanceOop");
    new_obj = InstanceKlass::register_finalizer(instanceOop(new_obj), CHECK_NULL);
    }

    return JNIHandles::make_local(env, oop(new_obj));
    JVM_END

    这段代码就是实现clone的jvm代码了,其中有这么一段

    // Check if class of obj supports the Cloneable interface.
    // All arrays are considered to be cloneable (See JLS 20.1.5)
    if (!klass->is_cloneable()) {
    ResourceMark rm(THREAD);
    THROW_MSG_0(vmSymbols::java_lang_CloneNotSupportedException(), klass->external_name());
    }

    这个就是检查继承Object的类是不是继承了Cloneable接口,如果is_cloneable()是flase,那就说明没有继承,那就会抛出CloneNotSupportedException异常。
    KlassHandle会在类加载的时候去检查是否有Cloneable这个接口(具体是如何检查的我没有看懂,深入学习jvm的时候再去了解)。这样就实现了没有继承Cloneable就会报错的这么一个机制。

  • 为什么要设置这层屏障?
    这个问题个人感觉是一种设计,就是用一个接口来标记该类是否需要clone.大佬们说这是一种设计缺陷。
    知乎R大的回答
    Effective Java Author

深入讨论-浅拷贝与深拷贝

浅拷贝:

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象
  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是一个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象,因此实际上两个对象的该成员变量都指向同一个实例。在这种情况下该成员变量会影响到另一个对象的该成员变量值
  • 我们使用Object的clone就是实现了浅拷贝。

    深拷贝

  • 复制对象的所有基本数据类型的成员变量
  • 为所有应用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,对象进行深拷贝要对整个对象进行深拷贝
  • 深拷贝实现方式1:重写clone方法实现深拷贝
  • 深拷贝实现方式2:通过对象序列化实现深拷贝
  • 深拷贝实现方式3:通过反射实现深拷贝

clone方式实现深拷贝

首先我们来实现一个类用于验证是否是深拷贝

public class DeepCloneableTarget implements Cloneable {
private static final long serialVersionUID = 1L;
public String cloneName;
public String cloneClass;//为了方便测试直接这样写了
//构造器
public DeepCloneableTarget(String cloneName, String cloneClass) {
this.cloneName = cloneName;
this.cloneClass = cloneClass;
}
//因为该类的属性,都是String , 因此我们这里使用默认的clone完成即可
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

然后我们创建一个原型类

public class DeepProtoType implements Cloneable{

public String name; //String 属性
public DeepCloneableTarget deepCloneableTarget;// 引用类型
public DeepProtoType() {
super();
}
//深拷贝 - 方式 1 使用clone 方法
@Override
protected Object clone() throws CloneNotSupportedException {

Object deep = null;
//这里完成对基本数据类型(属性)和String的克隆
deep = super.clone();
//对引用类型的属性,进行单独处理
DeepProtoType deepProtoType = (DeepProtoType)deep;
deepProtoType.deepCloneableTarget = (DeepCloneableTarget)deepCloneableTarget.clone();

// TODO Auto-generated method stub
return deepProtoType;
}
}
//测试
public class Client {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
DeepProtoType p = new DeepProtoType();
p.name = "宋江";
p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");
//方式1 完成深拷贝
DeepProtoType p2 = (DeepProtoType) p.clone();

System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
}
}

这是通过层层嵌套的clone实现深拷贝,对每个类都实现clone,到最顶层也就实现了深拷贝

序列化的方式实现deepclone

要序列化的对象需要实现Serializable接口,我们在DeepCloneableTarget加上Serializable

public class DeepCloneableTarget implements Serializable

改造DeepProtoType

public class DeepProtoType implements Serializable{
public String name; //String 属性
public DeepCloneableTarget deepCloneableTarget;// 引用类型
public DeepProtoType() {
super();
}
//深拷贝 - 方式2 通过对象的序列化实现 (推荐)
public DeepProtoType deepClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this); //当前这个对象以对象流的方式输出
//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
DeepProtoType copyObj = (DeepProtoType)ois.readObject();
return copyObj;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
return null;
} finally {
//关闭流
try {
bos.close();
oos.close();
bis.close();
ois.close();
} catch (Exception e2) {
// TODO: handle exception
System.out.println(e2.getMessage());
}
}
}
}
//测试
public class Client {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
DeepProtoType p = new DeepProtoType();
p.name = "宋江";
p.deepCloneableTarget = new DeepCloneableTarget("大牛", "小牛");
//方式2 完成深拷贝
DeepProtoType p2 = p.deepClone();
System.out.println("p.name=" + p.name + "p.deepCloneableTarget=" + p.deepCloneableTarget.hashCode());
System.out.println("p2.name=" + p.name + "p2.deepCloneableTarget=" + p2.deepCloneableTarget.hashCode());
}
}

通过序列化和反序列化的方式实现深拷贝,使用序列化的前提是需要所有要深拷贝得类实现Serializable接口

通过反射实现深拷贝

public class deepcloneTools {
// ------------入口-------------
public static Object copy(Object source) throws Exception{
Object o = null;
Class clazz = source.getClass();
o = clazz.newInstance();//这里使用的是无参构造函数,如果需要深拷贝的类中没有无参的构造器,会报错
while(clazz != Object.class){
Field fields[] = clazz.getDeclaredFields();
for(Field field : fields) {
if(Modifier.isStatic(field.getModifiers()))
continue;
field.setAccessible(true);
Object value = field.get(source);
// 基本类型
if(field.getType().isPrimitive()) {
field.set(o, value);
}
// 数组类型 因为数组类型也是 Object 的实例, 所以写在前面
else if(field.getType().isArray() ) {
field.set(o, operateArray(value));
}
// 不为null的对象
else if(value instanceof Object) {
field.set(o, copy(value));//要有set方法
}
field.setAccessible(false);
}
clazz = clazz.getSuperclass();
}
return o;
}

// 一维数组, 二维
public static Object operateArray(Object array) throws Exception{
// 1. 数组不为null, 2. 数组长度 >= 0
if(array == null)
return null;
int length = Array.getLength(array);
Class componentType = array.getClass().getComponentType();
// 基本类型 + String 类型, 因为String 类型的值是不变的, 所以采用等值
if(componentType.isPrimitive() || componentType == String.class) {
Object xxx = returnPrimitive(array, length);
return xxx;
}
// 保证长度 > 0
if(componentType.isArray()) {
// 固定长度的多维数组
Object value = Array.newInstance(array.getClass().getComponentType(), Array.getLength(array));
int len = Array.getLength(array);
for(int i = 0; i < len; i++) {
Array.set(value, i, operateArray(Array.get(array, i)));
}
return value;
}
else {
Object o = null;
o = Array.newInstance(componentType, length);
for(int i = 0; i < length; i++) {
Object value = copy(Array.get(array, i));
Array.set(o, i, value);
}
return o;
}
}

public static Object returnPrimitive(Object array, int length) {
Class componentType = array.getClass().getComponentType();
if(componentType == int.class)
return Arrays.copyOf((int[])array, length);
if(componentType == double.class)
return Arrays.copyOf((double[])array, length);
if(componentType == float.class)
return Arrays.copyOf((float[])array, length);
if(componentType == long.class)
return Arrays.copyOf((int[])array, length);
if(componentType == boolean.class)
return Arrays.copyOf((boolean[])array, length);
if(componentType == byte.class)
return Arrays.copyOf((byte[])array, length);
if(componentType == short.class)
return Arrays.copyOf((short[])array, length);
if(componentType == char.class)
return Arrays.copyOf((char[])array, length);
if(componentType == String.class)
return Arrays.copyOf((String[])array, length);
return null;
}
}

测试方法与之前的类似,只是换成了静态方法而已,这里不写代码了,这种反射的方式可以实现深拷贝。

原型模式小结

  • 创建新的对象比较复杂的时候,可以利用原型模式简化对象创建过程,同时也能够提高效率
  • 不用重新初始化对象,而是动态的获取对象运行时的状态
  • 如果原始对象发生变化(怎么或者减少属性),其他克隆对象也会发生相应的变化,无需修改代码
  • 在实现深拷贝的时候可能需要比较复杂的代码
  • 缺点:需要为每一个类装配一个克隆方法,这对全新的类来说不是很难,但是对已有的类进行改造时,需要修改其源代码,违背了ocp原则,这点注意。
文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/04/10/DesignPattern/Prototype/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论