当前位置:   article > 正文

设计模式三(原型模式)_scope=prototype原型模式

scope=prototype原型模式
原型模式

原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。

原型模式的核心在于拷贝原型对象,以系统中已存在的一个对象为原型,直接基于内存二进制流进行拷贝,无须再经历耗时的对象创建过程(不调用构造函数),性能提升许多。当对象的创建过程比较耗时,可以利用当前系统中已存在的对象作为原型,对其进行克隆(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大减少。

应用场景

你一定遇到过大篇幅的 getter、setter赋值的场景,比如这样的代码:
在这里插入图片描述
代码非常工整,命名非常规范,注释也写的很全面,其实这就是原型模式的需求场景。但是,大家觉得这样的代码优雅吗?我认为,这样的代码属于纯体力劳动,那原型模式能帮助我们解决这样的问题。
原型模式主要适用于一下场景:

  • 类初始化消耗资源较多;
  • new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等);
  • 构造函数比较复杂;
  • 循环体中产生大量对象时;

在 Spring 中,原型模式应用的非常广泛。例如:scope = “prototype”,我们经常使用的JSON.parseObject()也是一种原型模式。

原型模式的通用写法

下面来看一下原型模式的写法:

/**
* 自定义接口
*/
public interface IPrototype<T> {
    T clone();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

编写实现类,并实现 clone() 方法:

public class ConcretePrototype implements IPrototype {

	private int age;
	private String name;

	public int getAge() {
		return age;
	}

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

	public String getName() {
		return name;
	}

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

	@Override
	public ConcretePrototype clone() {
		ConcretePrototype concretePrototype = new ConcretePrototype();
		concretePrototype.setAge(this.age);
		concretePrototype.setName(this.name);
		return concretePrototype;
	}

	@Override
	public String toString() {
		return "ConcretePrototype{" +
				"age=" + age +
				", name='" + name + '\'' +
				'}';
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

下面进行测试:

public class PrototypeTest {


    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Jack");
        System.out.println(prototype);

        //拷贝原型对象
        ConcretePrototype cloneType = prototype.clone();
        System.out.println(cloneType);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

运行结果:
在这里插入图片描述
可以看出,结果是一样的。这个时候,有小伙伴就会问了,原型模式就这么简单吗?对,就是这么简单。在这个简单的场景之下,看上去操作好像变复杂了,但是如果有好几百个属性需要复制,那我们就可以一劳永逸。

上面的过程,是我们自己完成,其实JDK已经有写好的接口来实现原型模式了,不用我们自己来写接口再去自己实现,我们只需要实现Cloneable接口即可,来看 JDK 的实现:

@Data
public class ConcretePrototype implements Cloneable {

    private int age;
    private String name;
    /**
    * 新增个人爱好属性
    */
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }


    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", hobbies=" + hobbies +
                '}';
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

测试代码:

public class PrototypeTest {


    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Jack");
        List<String> hobbies = new ArrayList<>();
        hobbies.add("编程");
        hobbies.add("游戏");
        prototype.setHobbies(hobbies);

        //拷贝原型对象
        ConcretePrototype cloneType = prototype.clone();
        cloneType.getHobbies().add("技术控");

        System.out.println("原型对象:" + prototype);
        System.out.println("克隆对象:" + cloneType);
        System.out.println(prototype == cloneType);

        System.out.println("原型对象的爱好:" + prototype.getHobbies());
        System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

运行结果:
在这里插入图片描述
发现,我们给克隆后的对象新增了一个爱好,原型对象也发送了变化,这显然不符合我们的预期。因为我们希望克隆出来的对象应该和原型对象是两个独立的对象,不应该再有联系了,从测试结果来看,应该是 hobbies共用了一个内存地址,意味着复制的不是值,而是引用地址。这样的话,如果修改任意一个对象中属性的值,原型和克隆后的对象值都会变,这就是我们经常说的浅克隆。只是完整复制了值类型数据,没有复制引用对象,换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。那如何解决这个问题呢?我们使用深克隆来改造。

使用序列化完成深度克隆

在上面的代码中,我们继续改造,添加一个 deepClone()方法:

@Data
public class ConcretePrototype implements Cloneable, Serializable {

	private int age;
	private String name;
	private List<String> hobbies;

	@Override
	public ConcretePrototype clone() {
		try {
			return (ConcretePrototype) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 使用序列化来完成深度克隆
	 *
	 * @return
	 */
	public ConcretePrototype deepClone() {
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(this);

			ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bis);

			return (ConcretePrototype) ois.readObject();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}


	}

	@Override
	public String toString() {
		return "ConcretePrototype{" +
				"age=" + age +
				", name='" + name + '\'' +
				", hobbies=" + hobbies +
				'}';
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

测试代码:

    public static void main(String[] args) {
        //创建原型对象
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.setAge(18);
        prototype.setName("Jack");
        List<String> hobbies = new ArrayList<String>();
        hobbies.add("编程");
        hobbies.add("游戏");
        prototype.setHobbies(hobbies);

        //拷贝原型对象
        ConcretePrototype cloneType = prototype.deepClone();
        cloneType.getHobbies().add("技术控");

        System.out.println("原型对象:" + prototype);
        System.out.println("克隆对象:" + cloneType);
        System.out.println(prototype == cloneType);


        System.out.println("原型对象的爱好:" + prototype.getHobbies());
        System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
        System.out.println(prototype.getHobbies() == cloneType.getHobbies());

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

运行结果:
在这里插入图片描述
通过运行结果,发现我们得到了想要的结果。再来看看JDK 中 ArrayList里面的 clone()方法,源码如下:

/**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

再看 Arrays.copyOf()方法的实现:

public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
  • 1
  • 2
  • 3

发现是通过浅克隆的方式,只不过将每个元素进行了复制,我们使用 ArrayList.clone()进行改造,属于硬编码,代码如下:

@Data
public class ConcretePrototype implements Cloneable, Serializable {

	private int age;
	private String name;
	private List<String> hobbies;

	@Override
	public ConcretePrototype clone() {
		try {
			return (ConcretePrototype) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 使用 ArrayList 来完成深度克隆
	 *
	 * @return
	 */
	public ConcretePrototype deepCloneHobbies() {
		try {
			ConcretePrototype result = (ConcretePrototype) super.clone();
			result.hobbies = (List) ((ArrayList) result.hobbies).clone();
			return result;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 使用序列化来完成深度克隆
	 *
	 * @return
	 */
	public ConcretePrototype deepClone() {
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(this);

			ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bis);

			return (ConcretePrototype) ois.readObject();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public String toString() {
		return "ConcretePrototype{" +
				"age=" + age +
				", name='" + name + '\'' +
				", hobbies=" + hobbies +
				'}';
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

运行结果:
在这里插入图片描述
运行结果,依然如我们所预料的那样。

克隆破坏单例模式

如果我们克隆的目标对象是单例对象,那意味着深克隆就会破坏单例(可以参考我的上一篇文章来看如何破坏单例及防止破坏单例)。实际上,防止克隆破坏单例的解决思路非常简单,禁止深克隆即可。要么我们的单例类不实现Cloneable 接口;要么我们重写 clone 方法,直接返回单例的实例化对象;

	@Override
	public Object clone() {
		return INSTANCE;
	}
  • 1
  • 2
  • 3
  • 4
原型模式的优缺点

优点:

  1. 性能优良,Java自带的原型模式,是基于二进制流的拷贝,比直接 new 一个对象,性能上提升了许多;
  2. 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点:

  1. 需要为每一个类配置一个克隆方法;
  2. 克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则;
  3. 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多套嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深拷贝、浅拷贝需要运用得当。

好了,原型模式就写这么多,在实际应用中也会经常见到,比如:BeanUtils.copy…等,慢慢发掘吧!

上一篇:单例模式 https://blog.csdn.net/qq_20315217/article/details/114130214
下一篇:建造者模式 https://blog.csdn.net/qq_20315217/article/details/114282763

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/68470
推荐阅读
相关标签
  

闽ICP备14008679号