本期要和大家分享的是自己对于原型模式的理解,原型模式是创建型模式,所谓创建型模式可以简单理解为,是创建对象模式,创建型模式分享完后,从下篇文章开始,分享结构型模式。
什么是原型模式
**原型模式复制已有对象,而又无需使代码依赖它们所属的类。**在Java中只要使用了new关键字,那么这个就是定义中所说的依赖。依赖意味着耦合,而原型模式整好解决了这个问题。
入门小案例
该案例代码比较简洁,意让大家快速理解原型模式。Object这个类大家并不既陌生,它是所有类的父类,在Object中有个clone()方法,这个方法的功能就是拷贝对象。先来看看这个方法:
这是一个本地方法且抛出了一个异常,关于这个方法的作用,注释已经说的非常好了,英语好的小伙伴可以直接看,英语不好的只能吃下我的回锅肉了。注释的几个意思是该方法创建并返回此对象的副本,类的方法执行特定的克隆操作必须实现Cloneable接口,如果不实现,默认会抛出CloneNotSupportedException,数组对象默认实现了Cloneable接口。clone方法执行的是‘浅复制’,而不是‘深复制’。我们先来看看没实现Cloneable抛出异常的情况:
接着看下如何理解clone方法的浅拷贝以及如何实现深拷贝:
案例非常简单,定义一个类ShapeTest,两个普通类型的属性,一个对象类型属性,实现了Cloneable接口,重写clone方法。
@Data
public class ShapeTest implements Cloneable {
private int int1;
private int int2;
private Person person;
@Override
public Object clone() throws CloneNotSupportedException {
ShapeTest clone = (ShapeTest)super.clone();
return clone;
}
}
public class Person {
}
测试类
public class ShapeMainTest {
public static void main(String[] args) throws CloneNotSupportedException {
// 定义一个原型
ShapeTest shapeTest = new ShapeTest();
shapeTest.setInt1(1234);
shapeTest.setInt2(1234);
shapeTest.setPerson(new Person());
// 调用clone方法克隆新的对象
ShapeTest clone = (ShapeTest) shapeTest.clone();
// 比较地址值
System.out.println(shapeTest.getInt2() == clone.getInt2());
System.out.println(shapeTest.getInt1() == clone.getInt1());
System.out.println(shapeTest.getPerson() == clone.getPerson());
}
}
测试结果
true
true
true
我们看到三个结果都是true,那么对于简单类型来说,原型与克隆出来的对象是相互独立的,比如我修改克隆对象的简单属性的值,是不影响原型的。但是对于Person这种引用类型的属性,clone方法只是复制了引用的地址,而不是像普通类型进行逐位复制。
所以前两个true是没问题的,第三个就会出现问题,只要原型或者克隆的任一对象对person属性进行修改,那么所有的对象都会随之改变,这就是浅复制。
如何解决浅拷贝呢?
解决浅拷贝的方式其实很简单,就是让Person类也实现Cloneable接口,重写clone()方法,这样就能实现原型与克隆对象中的引用类型的属性相互独立。修改后的代码如下:
public class Person implements Cloneable {
@Override
public Object clone() throws CloneNotSupportedException {
Person clone = (Person)super.clone();
return clone;
}
}
@Data
public class ShapeTest implements Cloneable {
private int int1;
private int int2;
private Person person;
@Override
public Object clone() throws CloneNotSupportedException {
ShapeTest clone = (ShapeTest)super.clone();
clone.setPerson((Person) clone.getPerson().clone());
return clone;
}
}
测试结果
true
true
false
最后一个比较结果是false,这就是我们所说的深复制,深复制需要我们对引用类型的属性进行再克隆,这样才能达到我们想要的结果。如果是多层引用类型嵌套可能就需要使用递归来进行处理,但是递归比较消耗性能。
拓展知识点
原型模式一定要实现Cloneable接口吗?
设计模式是一套方法论,每个模式都在介绍一种思想,原型模式的核心思想就是在原型类中提供一个clone方法来克隆对象。JDK中的clone方法可以复制很多对象,但是我们也可以自定义接口或者抽象类,而不去实现Cloneable接口,具体的我们在下面这个知识点中体现。
原型工厂
在入门小案例中的原型是在我们测试里手动new的,并且手动克隆的。实际上我们可以定义一个原型工厂,在原型工厂类中定义一个缓存各种原型的容器,通过传递原型名称或者其他参数。工厂通过搜索参数返回克隆的对象。
下面的案例是原型是一个形状抽象类,创建圆形类与矩形类继承形状抽象类
形状原型类
public abstract class BaseShape {
public int x;
public int y;
public String color;
public BaseShape() {
}
public BaseShape(BaseShape target) {
if (target != null) {
this.x = target.x;
this.y = target.y;
this.color = target.color;
}
}
/**
* 克隆一个对象
*
* @return Object
*/
@Override
protected abstract BaseShape clone();
@Override
public boolean equals(Object shape2) {
if (!(shape2 instanceof BaseShape)) {
return false;
}
BaseShape baseShape = (BaseShape) shape2;
return (baseShape.y == y) && (baseShape.x == x) && Objects.equals(baseShape.color, color);
}
}
圆形类
@NoArgsConstructor
public class Circle extends BaseShape {
/**
* 半径
*/
public int radius;
public Circle(Circle target) {
super(target);
if (target != null) {
this.radius = target.radius;
}
}
@Override
protected Circle clone() {
return new Circle(this);
}
@Override
public boolean equals(Object object) {
if (!(object instanceof Circle)) {
return false;
}
Circle circle = (Circle) object;
return circle.radius == radius;
}
}
矩形类
@NoArgsConstructor
public class Rectangle extends BaseShape {
public int width;
public int height;
public Rectangle(Rectangle target) {
super(target);
if (target != null) {
this.width = target.width;
this.height = target.height;
}
}
@Override
protected Rectangle clone() {
return new Rectangle(this);
}
@Override
public boolean equals(Object object) {
if (!(object instanceof Rectangle)) {
return false;
}
Rectangle rectangle = (Rectangle) object;
return rectangle.width == width && rectangle.height == height;
}
}
原型工厂
public class BundledShapeCache {
public Map<String, BaseShape> cache = new HashMap<>();;
/**
* 原型工厂
*/
public BundledShapeCache() {
Circle circle = new Circle();
circle.x =35;
circle.y = 36;
circle.radius = 6;
circle.color = "pink";
cache.put("pink", circle);
Rectangle rectangle = new Rectangle();
rectangle.width = 10;
rectangle.height = 20;
rectangle.color = "blue";
rectangle.width = 32;
rectangle.height = 21;
cache.put("blue", rectangle);
}
public BaseShape get(String color) {
return cache.get(color).clone();
}
}
测试
public class Demo {
public static void main(String[] args) {
BundledShapeCache cache = new BundledShapeCache();
BaseShape pink = cache.get("pink");
BaseShape blue1 = cache.get("blue");
BaseShape blue2 = cache.get("blue");
if (pink != blue1) {
System.out.println("它们不是一种形状!");
}
if (blue1 != blue2) {
System.out.println("它们不是同一个对象!");
if (blue1.color.equals(blue2.color)) {
System.out.println("它们的值是相等的!");
}
}
}
}
结果
它们不是一种形状!
它们不是同一个对象!
它们的值是相等的!
总结
原型模式的应用在真实世界其实也是随处可见的,打印复印相比大家都知道。在打印店中复印比打印快,成本低。假设公司要打印1000份公司同事名单。那么打印1张复印999张,还是打印1000张?这就是原型模式,它的核心是在原型类中提供一个clone方法,但是需要注意浅复制与深复制的的问题。
设计模式学的是思想,在新学一种设计模式时,需要考虑到是否能和其他模式联系起来,比如抽象工厂,工厂方法,原型模式都可以与单例模式结合。有些模式是否还有递进的关系,比如创建型模式,我是从简单工厂演化成工厂方法,从工厂方法演化成抽象工厂。有兴趣的可以看看往期文章。