23个java设计模式(十三)-- 原型模式

2016年03月21日 原创
关键词: java 设计模式
摘要 原型模式用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

一、概述。

原型模式用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

在某些情况下,可能不希望反复使用类的构造方法创建许多对象,而是希望用该类创建一个对象后,以该对象为原型得到该对象的若干个复制品。也就是说,将一个对象定义为原型对象,要求该原型对象提供一个方法,使该原型对象调用此方法可以复制一个和自己有完全相同状态的同类型对象,即该方法“克隆”原型对象得到一个新对象。原型对象与以他为原型所克隆出的新对象可以分别独立地变化,也就是说,原型对象改变其状态不会影响到以它为原型所克隆出的新对象,反之也一样。

原型模式是从一个对象出发得到一个和自己有相同状态的新对象的成熟模式,该模式的关键是将一个对象定义为原型,并为其提供复制自己的方法。

二、java.lang.Ojbect类的clone方法。

1.clone()方法。

      java.lang包中的Object类提供了一个权限是protected的用于复制对象的clone()方法。我们知道java中所有的类都是java.lang包中Object类的子类或间接子类,因此java中所有的类都继承了这个clone()方法,但是由于clone()方法的访问权限是protected的,这就意味着,如果一个对象想使用该方法得到自己的一个复制品,就必须保证自己所在的类与Object类在同一个包中,这是不可能的,因为Java不允许用户编写的类拥有java.lang这样的包名。

2.clone()方法的重写与Cloneable接口。

      为了能让一个对象使用clone()方法,创建该对象的类可以重写clone()方法,并将访问权限提高为public权限,为了能使用被覆盖的clone()方法,只需在重写的clone()方法中使用关键字super调用Object类的clone()方法即可。也可以在创建对象的类中新定义一个复制对象的方法,将访问权限定义为public,并在该方法中调用Object类的clone()方法。

       另外,当对象调用Object类中的clone()方法时,JVM将会逐个复制该对象的成员变量,然后创建一个新的对象返回,所以JVM要求调用clone()方法的对象必须实现Cloneable接口。Cloneable接口中没有任何方法,该接口唯一的作用就是让JVM知道实现该接口的对象是可以被克隆的。

下面的程序演示了clone方法的使用:

Circle.java

public class Circle implements Cloneable{
	private double radius;

	public double getRadius() {
		return radius;
	}

	public void setRadius(double radius) {
		this.radius = radius;
	}
	
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

Example1Test.java

public class Example1Test {
	@Test
	public void testMain() {
		Circle c = new Circle();
		c.setRadius(186.123);
		try {
			Circle copy = (Circle)c.clone();
			System.out.println("circle对象中的数据:"+c.getRadius());
			System.out.println("circle克隆对象中的数据:"+copy.getRadius());
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
	}
}

3.深度克隆。

Object类中的clone()方法将复制当前对象变量中的值来创建一个新对象。如果当前对象的成员变量是一个对象,那么clone()方法仅仅复制了这个对象的引用,这样一来就涉及一个深度克隆问题。所以使用clone()方法复制对象有许多细节需要考虑。

下面的程序演示了深度克隆

Rectangle.java

public class Rectangle implements Cloneable{
	private double m,n;

	public Rectangle(double m, double n) {
		this.m = m;
		this.n = n;
	}
	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	public double getM() {
		return m;
	}
	public void setM(double m) {
		this.m = m;
	}
	public double getN() {
		return n;
	}
	public void setN(double n) {
		this.n = n;
	}
	
}

Geometry.java

public class Geometry implements Cloneable{
	private int height;
	private Rectangle rectangle;
	public Geometry(int height, Rectangle rectangle) {
		this.height = height;
		this.rectangle = rectangle;
	}
	
	@Override
	public Object clone() throws CloneNotSupportedException {
		Geometry object = (Geometry)super.clone();
		object.rectangle = (Rectangle) rectangle.clone();//深度克隆
		return object;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	public Rectangle getRectangle() {
		return rectangle;
	}

	public void setRectangle(Rectangle rectangle) {
		this.rectangle = rectangle;
	}
	
}

Example2Test.java

public class Example2Test {
	@Test
	public void testMain() throws CloneNotSupportedException {
		Geometry g = new Geometry(100, new Rectangle(10, 20));
		Geometry gCopy = (Geometry) g.clone();
		System.out.println("geometry对象中的rectangle的长和宽:");
		System.out.println(g.getRectangle().getM()+","+g.getRectangle().getN());
		System.out.println("geometry克隆对象中的rectangle的长和宽:");
		System.out.println(gCopy.getRectangle().getM()+","+gCopy.getRectangle().getN());
		g.getRectangle().setM(456.98);
		g.getRectangle().setN(123.15);
		System.out.println("geometry对象中的rectangle的长和宽:");
		System.out.println(g.getRectangle().getM()+","+g.getRectangle().getN());
		System.out.println("geometry克隆对象中的rectangle的长和宽:");
		System.out.println(gCopy.getRectangle().getM()+","+gCopy.getRectangle().getN());
		
	}
}

三、Serializable接口与克隆对象。

相对于clone()方法,Java又提供了一种较简单的解决方案,这个方案就是使用Serializable接口和对象流来复制对象。

如果希望得到对象object的复制品,必须保证该对象是可序列化的,即创建object对象的类必须实现Serializable接口。Serializable接口中的方法对程序是不可见的,因此实现该接口的类不需要实现额外的方法。

为了得到object的复制品,首先需要将object写入ObjectOutputStream流中,当把一个序列化的对象写入ObjectOutputStream输出流时,JVM就会实现Serializable接口中的方法,将一定格式的文本--对象的序列化信息,写入ObjectOutputStream输出流的目的地。然后使用ObjectInputStream对象输入流读取对象回对象的序列化信息,并根据序列化信息创建一个新的对象,这个新对象就是object对象的一个复制品。

需要注意的是,使用对象流把一个对象写入文件时不仅要保证该对象是可序列化的,而且该对象的成员对象也必须是可序列化的。

四、原型模式的结构。

原型模式的结构中包括四种角色。

  • 抽象原型(Prototype):一个接口,负责定义对象复制自身的方法。
  • 具体原型(ConcretePrototype):实现Prototype接口的类。具体原型实现抽象原型中的方法,以便所创建的对象调用该方法复制自己。

五、示例程序。

抽象原型(Prototype)

Prototype.java

public interface Prototype {
	public Object cloneMe() throws CloneNotSupportedException;
}

具体原型(ConcretePrototype)

Cubic.java

public class Cubic implements Prototype,Cloneable{
	private double length, width, height;
	
	public Cubic(double length, double width, double height) {
		this.length = length;
		this.width = width;
		this.height = height;
	}

	@Override
	public Object cloneMe() throws CloneNotSupportedException {
		return clone();
	}

	public double getLength() {
		return length;
	}

	public void setLength(double length) {
		this.length = length;
	}

	public double getWidth() {
		return width;
	}

	public void setWidth(double width) {
		this.width = width;
	}

	public double getHeight() {
		return height;
	}

	public void setHeight(double height) {
		this.height = height;
	}

}

Goat.java

public class Goat implements Prototype,Serializable{
	private StringBuffer color;
	
	public StringBuffer getColor() {
		return color;
	}

	public void setColor(StringBuffer color) {
		this.color = color;
	}
	
	@Override
	public Object cloneMe() throws CloneNotSupportedException {
		Object o = null;
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(baos);
			oos.writeObject(this);
			ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bais);
			o = ois.readObject();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return o;
	}

}

测试程序

Example3Test.java

public class Example3Test {
	@Test
	public void testMain() throws CloneNotSupportedException {
		Cubic cubic = new Cubic(12,20,66);
		System.out.println("cubic的长、宽和高:"+cubic.getLength()+","+cubic.getWidth()+","+cubic.getHeight());
		Cubic cubicCopy = (Cubic) cubic.cloneMe();
		System.out.println("cubicCopy的长、宽和高:"+cubicCopy.getLength()+","+cubicCopy.getWidth()+","+cubicCopy.getHeight());
		Goat goat = new Goat();
		goat.setColor(new StringBuffer("白颜色的山羊"));
		System.out.println("goat是"+goat.getColor());
		Goat goatCopy = (Goat) goat.cloneMe();
		System.out.println("goatCopy是"+goatCopy.getColor());
		System.out.println("goatCopy将自己的颜色变为黑色");
		goatCopy.setColor(new StringBuffer("黑颜色的山羊"));
		System.out.println("goat是"+goat.getColor());
		System.out.println("goatCopy是"+goatCopy.getColor());
	}
}

六、原型模式的优点。

  1. 当创建类的新实例的代价更大时,使用原型模式复制一个已有的实例可以提高创建新实例的效率。
  2. 可以动态地保存当前对象的状态。在运行时,可以随时使用对象流保存当前对象的一个复制品。
  3. 可以在运行时创建新的对象,而无须创建一系列类和继承结构。
  4. 可以动态地添加、删除原型的复制品。

七、适合使用原型模式的情景。

  1. 程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时。
  2. 当对象的创建需要独立于它的构造过程和表示时。
  3. 一个类创建的实例不是很多,那么久可以将这个类的一个实例定义为原型,那么通过复制该原型得到新的实例可能比重新使用类的构造方法创建新实例更方便。

更多的例子可以访问博主的github:https://github.com/cxylpy/design-patterns