写点什么

说说常常被研发忽略的原型模式

发布于: 2021 年 02 月 02 日
说说常常被研发忽略的原型模式

1、写在开头

创建型模式是用来创建对象的模式,抽象了实例化的过程,帮助一个系统独立于其他关联对象的创建、组合和表示方式。

原型模式目的:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,进而减少在创建新对象上的性能开销。

1.1 相关文章

原型模式也是创建型的设计模式之一,本文是设计模式系列(共24节)的第 4 篇文章。

设计模式都从六大原则出发进行总结:《第一节:设计模式的六大原则

创建型设计模式共 5 种:


2、原型模式是什么

Prototype(原型模式)属于创建型模式,既不是工厂也不是直接 New,而是以拷贝的方式创建对象。


原理:Object 类的 clone() 方法是一个本地方法[参考:《JDK源码:Object类》],它可以直接操作内存中的二进制流,所以性能相对 new 实例化来说,更加优秀。(由于 Java 提供了对象拷贝的 clone() 方法,所以用 Java 实现原型模式很简单)


结构图:



Client 是发出指令的客户端,Prototype 是一个接口,描述了一个对象如何克隆自身,比如必须拥有 clone() 方法,而 ConcretePrototype 就是克隆具体的实现,不同对象有不同的实现来拷贝自身。

3、原型模式的应用

我们通过抽象类定义了三种图形形状:三角形、圆形、矩形。图形的抽象类 Shape 实现了 Cloneable 接口,并且覆盖了 clone 方法,是 Java 版本原型模式的通用做法。

举例子 1:

我们通过抽象类定义了三种图形形状:三角形、圆形、矩形。

图形的抽象类 Shape 实现了 Cloneable 接口,并且覆盖了 clone 方法,是 Java 版本原型模式的通用做法。(克隆方法并没有调用对象的构造器,而是通过内存操作,完成空间分配)



public abstract class Shape implements Cloneable { protected String type; // 定义动作 abstract void draw(); public void setType(String type) { this.type = type; } public String getType(){ return type; } //克隆方法 public Object clone() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; }}public class Circle extends Shape { public Circle(){ type = "Circle"; } @Override public void draw() { System.out.println("Inside Circle::draw() method.");
}}public class Square extends Shape { public Square(){ type = "Square"; } @Override public void draw() { System.out.println("Inside Square::draw() method."); }}public class Rectangle extends Shape { public Rectangle(){ type = "Rectangle"; } @Override public void draw() { System.out.println("Inside Rectangle::draw() method."); }}
复制代码


接着我们对外提供一个工厂方法类:ShapeCacheFactory,它的作用是:初始化缓存池的原型对象 & 对外提供创建新对象的工厂方法 API。



public class ShapeCacheFactory {
//缓存池,保存多种图形的唯一对象。 private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>();
//结合工厂方法模式使用:当需要相同类型的图形时,从缓存池中取出对应的图形,然后复制一份给客户端。 public static Shape getShape(String shapeId) { Shape cachedShape = shapeMap.get(shapeId); return (Shape) cachedShape.clone(); }
// 添加三种形状到缓存池:三角形、圆形、矩形 public static void loadCache() { Circle circle = new Circle(); circle.setType("Circle"); shapeMap.put(circle.getType(),circle);
Square square = new Square(); square.setType("Square"); shapeMap.put(square.getType(),square);
Rectangle rectangle = new Rectangle(); rectangle.setType("Rectangle"); shapeMap.put(rectangle.getType(),rectangle); }}
复制代码


测试用例:


public class TestPrototypePattern {  public static void main(String[] args) {    //initialization    ShapeCacheFactory.loadCache();
//Circle Square Rectangle Shape clonedShape = (Shape) ShapeCacheFactory.getShape("Circle"); System.out.println("Shape : " + clonedShape.getType());
Shape clonedShape2 = (Shape) ShapeCacheFactory.getShape("Square"); System.out.println("Shape : " + clonedShape2.getType());
Shape clonedShape3 = (Shape) ShapeCacheFactory.getShape("Rectangle"); System.out.println("Shape : " + clonedShape3.getType()); }}
复制代码


结果输出:


Shape : CircleShape : SquareShape : Rectangle
复制代码


原型模式的优点

  • Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。因为相对 new 来说,clone() 少了调用构造函数。如果构造函数中存在大量属性初始化或大对象,则使用 clone() 的复制对象的方式性能会好一些。


原型模式的缺点

  • 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

  • 必须实现 Cloneable 接口。

4、原型模式在 spring 的使用

原型模式在开源框架中的应用也非常广泛,在使用了 Spring 的 web 工程中,除非特殊情况,我们都会选择使用 Spring 的 IOC 功能来管理 Bean,而不是用到时去 new 一个。例如 Spring 中,@Service 默认都是单例的。

  Spring 定义了多种作用域,可以基于这些作用域创建 bean,包括:

  • 单例( Singleton):在整个应用中,只创建 bean 的一个实例。 

  • 原型( Prototype):每次注入或者通过 Spring 应用上下文获取的时候,都会创建一个新的 bean 实例。

因为 Spring 管理的 Bean 默认是单例的(即 Spring 创建好 Bean,需要时就拿来用,而不是每次用到时都去 new,又快性能又好),但有时候单例并不满足要求(例如出现的线程安全问题,参考《单例模式》),这时可以使用@Scope("prototype")注解,可以通知 Spring 把被注解的 Bean 变成多例。(篇幅所限,后续讲解 spring 源码时再一起详细探讨)

5、总结

本节主要讲解了原型模式的实现方法和优缺点,另外补充了 spring 源码使用到的原型模式案例:@Scope("prototype")。一般开发中,我们遇到需要大量复制同一对象的场景,使用原型模式都是不错的选择,且效率好。

另外,创建型设计模式还包括:抽象工厂模式和建造者模式,待后续我们再细细解读。

6、延伸阅读

《源码系列》

JDK之Object 类

JDK之BigDecimal 类

JDK之String 类

JDK之Lambda表达式


《经典书籍》

Java并发编程实战:第1章 多线程安全性与风险

Java并发编程实战:第2章 影响线程安全性的原子性和加锁机制

Java并发编程实战:第3章 助于线程安全的三剑客:final & volatile & 线程封闭


《服务端技术栈》

《Docker 核心设计理念

《Kafka史上最强原理总结》

《HTTP的前世今生》


《算法系列》

读懂排序算法(一):冒泡&直接插入&选择比较

《读懂排序算法(二):希尔排序算法》

《读懂排序算法(三):堆排序算法》

《读懂排序算法(四):归并算法》

《读懂排序算法(五):快速排序算法》

《读懂排序算法(六):二分查找算法》


《设计模式》

设计模式之六大设计原则

设计模式之创建型(1):单例模式

设计模式之创建型(2):工厂方法模式

设计模式之创建型(3):原型模式

设计模式之创建型(4):建造者模式



发布于: 2021 年 02 月 02 日阅读数: 23
用户头像

Diligence is the mother of success. 2018.03.28 加入

公众号:后台技术汇 笔者主要从事Java后台开发,喜欢技术交流与分享,保持饥渴,一起进步!

评论

发布
暂无评论
说说常常被研发忽略的原型模式