原型模式
1、什么是原型模式?
原型模式(Prototype Pattern)是一种用于创建重复的对象,同时又能保证性能的设计模式。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。或者说:原型模式指原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
2、原型模式的特性
(1)意图:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
(2)主要解决:
在运行期建立和删除原型。
(3)何时使用:
1、当一个系统应该独立于它的产品创建,构成和表示时。
2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。
3、为了避免创建一个与产品类层次平行的工厂类层次时。
4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
(4)如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。
(5)关键代码:
1、实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),或通过序列化的方式来实现深拷贝。
2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。
3、原型模式的优缺点及其应用场景
(1)优点:
1、性能提高。
2、逃避构造函数的约束。
(2)缺点:
1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
2、必须实现 Cloneable 接口。
(3)使用场景:
1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
(4)注意事项:
与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。
4、原型模式使用实现示例
(1)场景需求说明:
创建一个抽象类Shape和其具体子类;定义一个类ShapeCache,用于吧shape对象存储在散列表中,并在请求的时候返回他们的克隆。
(2)示例代码:
实现Cloneable接口的抽象类Shape类:
abstract class Shape implements Cloneable{
private String id;
//使用defalut使其在其子类中可以访问
protected String type;
abstract void draw();
public String getType(){
return type;
}
public String getId(){
return id;
}
public void setId(String id){
this.id = id;
}
//必须要用克隆方法,通过克隆来实现对象的原型复制
@Override
public Object clone(){
Object clone = null;
try{
clone = super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return clone;
}
}
具体子类实例:
//下面为三个扩展类上述抽象类的子类实体
class Rectangle extends Shape{
public Rectangle(){
type = "Rectangle";
}
@Override
void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
class Square extends Shape{
public Square(){
type = "Square";
}
@Override
void draw() {
System.out.println("Inside Square::draw() method.");
}
}
class Circle extends Shape{
public Circle(){
type = "Circle";
}
@Override
void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
创建一个类,将实体类存储在一个散列表中:
//创建一个类,将实体类存储在一个散列表中
class ShapeTable{
private static Map<String,Shape> shapeHashMap = new HashMap<String, Shape>();
//根据id获取一个原型实例
public static Shape getShape(String shapeId){
Shape shape = shapeHashMap.get(shapeId);
return (Shape)shape.clone();
}
//下面模拟数据操作,假设从数据库中获取数据,并创建该形状,假设要添加三种形状
public static void loadCache(){
Circle circle = new Circle();
circle.setId("1");
shapeHashMap.put(circle.getId(),circle);
System.out.println("origin Circle hascode:"+circle.hashCode());
Square square = new Square();
square.setId("2");
shapeHashMap.put(square.getId(),square);
System.out.println("origin Square hascode:"+square.hashCode());
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeHashMap.put(rectangle.getId(),rectangle);
System.out.println("origin Rectangle hascode:"+rectangle.hashCode());
}
}
测试示例:我们先将子类实体实例加载进来,后续根据需要克隆该实体实例来生成一个新对象放回使用:
public class PrototypeTest {
public static void main(String[] args) {
//测试示例:通过ShapeTable来获取存储在Map中的形状的克隆
ShapeTable.loadCache();
Shape cloneCircle = ShapeTable.getShape("1");
System.out.println("Shape : " + cloneCircle.getType()+" "+cloneCircle.hashCode());
cloneCircle.draw();
Shape cloneSquare = ShapeTable.getShape("2");
System.out.println("Shape : " + cloneSquare.getType()+" "+cloneSquare.hashCode());
cloneCircle.draw();
Shape cloneRectangle = ShapeTable.getShape("3");
System.out.println("Shape : " + cloneRectangle.getType()+" "+cloneRectangle.hashCode());
cloneRectangle.draw();
}
}
测试结果如下:
可以发现,新对象是旧对象的克隆。
5、原型模式在Java框架中的典型应用示例
(1)在SpringBoot框架中,Controller类型类是单例的,如果声明了一个普通类成员变量,所有的类方法都可以对其修改;在多线程并发情况下,当一个线程修改了成员变量的值时,另一个线程获取到的就是修改后的值,显然这不是我们需要的,如果我们需要让它们线程隔离,这时就可以将该Controller类设置成多例的:
@Scope("prototype")
本文源代码:
https://github.com/JianfuYang/2020-yjf-review/tree/master/src/designpatterns/prototype
声明:本文部分内容整理来源于网络,仅做个人学习使用!侵删~
本文部分内容参考链接:
https://www.runoob.com/design-pattern/prototype-pattern.html