22 设计模式:克隆模式(原型模式)--创建型

164 阅读6分钟

克隆模式,目前在实际的项目中,我还没有见到和使用到,虽然业务有处理服务端传输过来的数据对象,但是是通过json对象直接解析替换,这样会存在数据真空期,理论上来说是需要使用克隆模式,保持项目的健壮性。目前,只能看看定义,总结几个简单的例子,知道什么是克隆模式,后续项目中遇到了,再做详细的补充。

创建型模式之:

  • 单例模式
  • 工厂模式
  • 构造者模式
  • 克隆模式

原型模式适合应用场景

  • 如果你需要复制一些对象, 同时又希望代码独立于这些对象所属的具体类, 可以使用原型模式。

  • 这一点考量通常出现在代码需要处理第三方代码通过接口传递过来的对象时。 即使不考虑代码耦合的情况, 你的代码也不能依赖这些对象所属的具体类, 因为你不知道它们的具体信息。

原型模式为客户端代码提供一个通用接口, 客户端代码可通过这一接口与所有实现了克隆的对象进行交互, 它也使得客户端代码与其所克隆的对象具体类独立开来。

1.什么是克隆模式?

克隆模式是通过克隆直接获得某些已经存在的对象。而无需通过标准的构造方法来创建。原型模式通常包含一个原型接口或抽象类,该接口或抽象类定义了一个克隆方法,用于复制对象。具体的对象实现该接口或继承该抽象类,并实现克隆方法以完成对象的复制。

代码例子: 假设我们有一个图形编辑器,用户可以在画布上绘制不同的图形,包括圆形、矩形等。在某些情况下,用户需要复制已经绘制好的图形,并在其他位置粘贴。这时候,原型模式就可以派上用场。我们可以定义一个图形接口,其中包含一个克隆方法,然后每个具体的图形类(如圆形、矩形)都实现了这个接口,并在克隆方法中完成对象的复制。

以下是一个简化的示例代码:

// 定义图形接口
interface Shape extends Cloneable {
    Shape clone();
    void draw();
}

// 具体图形类:圆形
class Circle implements Shape {
    private int radius;

    public Circle(int radius) {
        this.radius = radius;
    }

    @Override
    public Shape clone() {
        return new Circle(this.radius);
    }

    @Override
    public void draw() {
        System.out.println("Drawing Circle with radius " + radius);
    }
}

// 具体图形类:矩形
class Rectangle implements Shape {
    private int width;
    private int height;

    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public Shape clone() {
        return new Rectangle(this.width, this.height);
    }

    @Override
    public void draw() {
        System.out.println("Drawing Rectangle with width " + width + " and height " + height);
    }
}

// 客户端代码
public class PrototypePatternExample {
    public static void main(String[] args) {
        // 创建原型对象
        Shape circlePrototype = new Circle(10);
        
        // 复制原型对象
        Shape clonedCircle = circlePrototype.clone();
        
        // 绘制复制的圆形
        clonedCircle.draw();
    }
}

在上面的示例中,我们定义了一个图形接口 Shape,其中包含了克隆方法 clone() 和绘制方法 draw()。具体的图形类 CircleRectangle 分别实现了这个接口,并在 clone() 方法中完成了对象的复制。客户端代码可以通过调用原型对象的 clone() 方法来复制对象,并进行绘制操作。

2. 原型模式的实现方式:深拷贝和浅拷贝

浅拷贝和深拷贝的区别在于,浅拷贝只会复制图中的索引(散列表),不会复制数据本身。相反,深拷贝不仅仅会复制索引,还会复制数据本身。浅拷贝得到的对象跟原始对象共享数据,而深拷贝得到的是一份完完全全独立的对象。

通过网上的一张图,来明确是什么是深拷贝与浅拷贝。

image.png

浅拷贝

image.png

深拷贝

浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象 深拷贝得到的是一份完全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。

如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,如果数据量不大,并且对项目中传入的数据没有确定性把控,还是优先使用深拷贝,数据量大的话,可以使用差量拷贝,只克隆变化的那一部分

3.克隆模式的实现方式

  1. 手动实现深拷贝:对于自定义的对象,可以手动编写代码来实现深拷贝。这需要在对象中重写 clone() 方法,确保在拷贝对象时所有的引用类型成员变量也进行了深拷贝。这种方法需要开发人员仔细管理对象内部的所有引用关系,确保深拷贝的完整性。

  2. 使用序列化和反序列化:将对象序列化为字节流,然后再从字节流中反序列化出一个新的对象。这种方法可以实现比较通用的深拷贝,但是要求对象及其所有引用的对象都必须是可序列化的。同时,序列化和反序列化的性能开销比较大。

  3. 使用第三方库:一些第三方库提供了深拷贝的实现,比如Apache Commons工具包中的 SerializationUtils.clone() 方法,以及Google Guava库中的 Objects.deepCopy() 方法。这些方法可以简化深拷贝的实现,但需要添加额外的依赖。

  4. 使用对象复制工具:一些对象复制工具可以帮助开发人员实现深拷贝,例如Apache BeanUtils库中的 BeanUtils.cloneBean() 方法,以及Spring框架中的 ObjectUtils.clone() 方法。这些工具可以自动处理对象内部的引用关系,实现深度复制。

4.克隆模式的优缺点

  • 你可以克隆对象, 而无需与它们所属的具体类相耦合。

  • 你可以克隆预生成原型, 避免反复运行初始化代码。

  • 你可以更方便地生成复杂对象。

  • 你可以用继承以外的方式来处理复杂对象的不同配置。

  • 克隆包含循环引用的复杂对象可能会非常麻烦