原型模式是指通过原型实例指定要创建对象的类型,并通过拷贝原型实例创建新对象。原型模式之创建型设计模式,它提供一种创建对象的最佳实践。拷贝即clone(克隆),分为浅拷贝和深拷贝两种。
浅拷贝:创建一个对象,新对象的属性和原对象完全相同,对于非基本类型对象,新对象的数据的引用仍指向原对象的属性引用指向的内存地址。简单来说,浅拷贝只克隆原对象本身和其包含的非引用类型属性,引用类型属性克隆。
深拷贝:创建一个对象,新对象的属性也会被拷贝,不在指向原有的对象地址。也就说通过深拷贝克隆的对象和原型对象完全独立开来,互补影响。
1.当创建对象的过程较为复制且比较耗时时,使用原型对象可以高效的创建对象。
2.原型模式允许动态添加和删除新类型对象,而无需更改现有代码,提高了系统的可扩展性。
1.当系统需要独立的它的产品创建,构成和表示时。
2.当需要实例化的类型需要在程序运行期间指定时。
3.当一个类的实例只能是几个状态的组合时,建立相同数量的原型并在每次需要时克隆他们,比每次根据状态实例化它来得方便。
4.和工厂模式结合使用,在实际项目中原型模式很少单独使用,一般是使用原型模式克隆对象后,有工厂模式返回给调用者使用。
1.Spring Framework ,在spring框架中当bean的作用于被设置为prototype时,每次请求都会创建一个新的bean实例,而不是从容器中获取bean实例。
2.Apache Common Lang这个库提供了SerializableUtils类,它使用原型模式克隆试下了Serializable接口的对象,它允许开发者通过序列化或反序列化创建对象的副本。
3.java集合框架中,clone()方法也是原型模式的引用,它允许开发者快发创建集合的副本,避免逐个添加元素。
在Java中,我们可以通过实现Cloneable接口,并重写clone()方法来实现原型模式,Cloneable是一个标记方法,实现它的类,表示该类的实例对象可以被复制,如果没实现Cloneable接口而直接调用clone()方法,程序会抛出CloneNotSupportedException异常。当然我们也可实现自己的抽象原型接口。
//定义抽象原型接口<形状>,并实现Cloneable接口,表示可以被克隆
public abstract class Shape implements Cloneable {
private String id;
private String type;
private List coordinates;
abstract void draw();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public List getCoordinates() {
return coordinates;
}
public void setCoordinates(ArrayList coordinates) {
this.coordinates = coordinates;
}
@Override
protected Shape clone() {
Shape shape = null;
try {
shape = (Shape) super.clone();
//如果要进行深拷贝,需要手动拷贝每个引用类型属性
shape.coordinates = (List) ((ArrayList) this.coordinates).clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return shape;
}
}
//定义具体原型类<圆形>
public class Circle extends Shape {
public Circle() {
type = "圆形";
}
@Override
void draw() {
System.out.println("绘制圆形");
}
}
/定义具体原型类<矩形>
public class Rectangle extends Shape {
public Rectangle() {
type = "矩形";
}
@Override
void draw() {
System.out.println("绘制矩形...");
}
}
public class ShapeCache {
//缓存原型对象
private static final HashMap shapeMap = new HashMap<>();
//从缓存中取出原型对象克隆创建新对象并返回
public static final Shape getShape(String shapeId) {
Shape shape = shapeMap.get(shapeId);
return shape.clone();
}
//加载原型对象。实际项目中,该对象可以从数据库获取,并在使用之前提前缓存好
public static void loadCache() {
Shape circle = new Circle();
circle.setId("1");
List circleCoordinates = new ArrayList<>();
circleCoordinates.add(1);
circleCoordinates.add(2);
circleCoordinates.add(10);
circle.setCoordinates(circleCoordinates);
shapeMap.put(circle.getId(), circle);
Shape rectangle = new Rectangle();
rectangle.setId("2");
List rectangleCoordinates = new ArrayList<>();
rectangleCoordinates.add(1);
rectangleCoordinates.add(2);
rectangleCoordinates.add(10);
rectangleCoordinates.add(12);
rectangle.setCoordinates(rectangleCoordinates);
shapeMap.put(rectangle.getId(), rectangle);
}
}
public class PrototypeClient {
public static void main(String[] args) {
//加载原型实例
ShapeCache.loadCache();
//克隆一个圆形对象
Shape circle = ShapeCache.getShape("1");
//克隆两个矩形对象
Shape rectangle1 = ShapeCache.getShape("2");
Shape rectangle2 = ShapeCache.getShape("2");
System.out.println("圆形:" + circle.getType());
System.out.println("矩形1:" + rectangle1.getType());
System.out.println("矩形2:" + rectangle2.getType());
System.out.println("矩形1==矩形2:" + (rectangle1 == rectangle2));
System.out.println("矩形1组标==矩形2坐标:" + (rectangle1.getCoordinates() == rectangle2.getCoordinates()));
}
}
运行结果如下:
原型模式通过提供一个原型实例来创建对象,调用者只需要关注如果使用这个克隆对象,而不必关心对象创建逻辑,这样将对象的创建和使用分离,使每个类的职责更加单一。
原型模式允许在不修改已有代码的情况下引入新类型的,通过克隆创建对象。可以使系统在运行时动态的添加和删除新类型的对象,提高了系统的可扩展性。
调用者依赖抽象的原型接口,而不依赖具体原型类。使得系统更加灵活,更易维护。
原型模式通过克隆已有的对象创建新对象,这是一种合成的方式,可以有效利用系统已有资源。