设计模式-原型模式

148 阅读6分钟

定义

原型模式(Prototype Pattern)是一种创建型设计模式,用于通过复制现有对象来创建新对象,而无需依赖显式的构造函数。原型模式通过克隆已有对象来创建新对象,使得对象的创建更加灵活和高效。

步骤

Java 中的原型模式通常使用 Cloneable 接口和 clone() 方法来实现。具体步骤如下:

  1. 创建一个可被复制的原型类,并实现 Cloneable 接口。
  2. 在原型类中实现 clone() 方法,该方法会复制对象的属性值并返回一个新的副本。
  3. 在需要创建新对象的地方,通过调用原型对象的 clone() 方法来获取一个新的对象副本。

优点:

  • 可以动态创建对象,避免了显式的实例化过程。
  • 可以简化对象创建过程,提高了创建对象的效率。
  • 可以通过复制现有对象来创建新对象,避免了重复初始化的过程。

注意事项:

  • 需要实现 Cloneable 接口,并重写 clone() 方法。
  • 在使用原型模式时,需要注意对象属性的克隆方式,确保复制的是属性的副本而不是引用。
  • 原型模式适用于创建复杂对象或需要频繁创建相似对象的场景,不适用于创建简单对象或创建过程简单的场景。

Coding

下面是一个使用原型模式的示例,假设有一个 Cat 类:

@Data
public class Cat implements Cloneable{
    private String name;

    private Date birthday;

    public Cat(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Cat cat = (Cat) super.clone();
        //cat.birthday = (Date) cat.birthday.clone();
        return cat;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}'+super.toString();
    }
}

现在我们创建一个 Cat 对象并进行浅克隆:

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Cat cat = new Cat("中华田园猫", new Date(666L));
        Cat cat2 = (Cat) cat.clone();
        System.out.println(cat1);
        System.out.println(cat2);
        cat.getBirthday().setTime(999999999999L);
        System.out.println(cat1);
        System.out.println(cat2);
    }
}

输出结果如下:

Cat1{name='中华田园猫', birthday=Thu Jan 01 08:00:00 CST 1970}com.geely.design2.pattern.creational.prototype.clone.Cat@6ce5412
Cat2{name='中华田园猫', birthday=Thu Jan 01 08:00:00 CST 1970}com.geely.design2.pattern.creational.prototype.clone.Cat@6ce5412
Cat1{name='中华田园猫', birthday=Sun Sep 09 09:46:39 CST 2001}com.geely.design2.pattern.creational.prototype.clone.Cat@db73608f
Cat2{name='中华田园猫', birthday=Sun Sep 09 09:46:39 CST 2001}com.geely.design2.pattern.creational.prototype.clone.Cat@db73608f

浅克隆

可以看到,通过浅克隆创建的 cat2 对象与原始对象 cat1 共享相同的属性值。当我们修改 cat2 的属性时,原始对象 cat1 的属性也会被修改,因为它们引用相同的对象。

这就是浅克隆的特点:它仅复制对象的字段值,而不复制对象引用的其他对象。因此,如果对象包含对其他对象的引用,那么浅克隆只会复制引用,而不会创建新的独立对象

深克隆

打开上面代码的注释

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Cat cat = (Cat) super.clone();
        cat.birthday = (Date) cat.birthday.clone();
        return cat;
    }

再次执行刚才的 main方法,结果如下:

Cat1{name='中华田园猫', birthday=Thu Jan 01 08:00:00 CST 1970}com.geely.design2.pattern.creational.prototype.clone.Cat@6ce5412
Cat2{name='中华田园猫', birthday=Thu Jan 01 08:00:00 CST 1970}com.geely.design2.pattern.creational.prototype.clone.Cat@6ce5412
Cat1{name='中华田园猫', birthday=Thu Jan 01 08:00:00 CST 1970}com.geely.design2.pattern.creational.prototype.clone.Cat@6ce5412
Cat2{name='中华田园猫', birthday=Sun Sep 09 09:46:39 CST 2001}com.geely.design2.pattern.creational.prototype.clone.Cat@db73608f

可以看到,通过深克隆创建的对象修改属性后,对象发生改变。

可以看到,通过深克隆,cat2 对象是 cat1 对象的独立副本,对 cat1 的修改不会影响 cat2。在 cat1 中修改了生日,但是 cat2 保持了克隆时的原始状态,没有被修改。这就是深克隆与浅克隆的区别,深克隆创建了对象及其关联对象的完全独立副本。

破坏单例

原型模式在某些情况下可能会破坏单例模式的限制。单例模式是一种创建型设计模式,旨在确保一个类只有一个实例,并提供全局访问点。

当原型模式被应用于单例类时,复制原型对象可能会创建新的对象实例,从而破坏了单例的唯一性。这是因为原型模式通过复制已有对象来创建新对象,而不是通过类的构造函数进行实例化。

下面是一个示例,展示了原型模式如何破坏单例模式:

javaCopy codeclass Singleton implements Cloneable {
    private static Singleton instance;

    private Singleton() {
        // 私有构造函数
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Main {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();

        try {
            Singleton instance2 = (Singleton) instance1.clone();

            System.out.println("Instance 1: " + instance1);
            System.out.println("Instance 2: " + instance2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

graphqlCopy codeInstance 1: Singleton@5c29bfd
Instance 2: Singleton@15db9742

在上述示例中,我们创建了一个单例类 Singleton,并在 main() 方法中尝试通过原型模式复制单例对象。通过调用 instance1.clone() 方法,我们成功创建了 instance2,这导致了两个不同的对象实例。

原型模式破坏了单例模式的限制,因为原型模式复制了已有对象,创建了一个新的对象实例。这导致原本应该是唯一的单例对象出现了多个实例。

为了防止原型模式破坏单例模式,可以在原型类的 clone() 方法中抛出异常或返回单例对象本身,以确保无法复制单例对象。或者,在实现原型模式时,要小心在设计和使用上的细节,确保不会意外地破坏单例的唯一性。

应用场景

原型模式在项目中有许多应用场景,其中一些常见的场景包括:

  1. 对象的创建成本高:当创建一个对象需要复杂的计算或资源消耗时,使用原型模式可以通过复制现有对象来避免重复的计算或资源获取,从而提高性能。
  2. 对象的创建过程复杂:当对象的创建过程涉及多个步骤、依赖关系或配置参数时,使用原型模式可以避免这些复杂性,通过复制一个已有对象,可以快速获得一个相似的对象,而无需重新执行复杂的创建过程。
  3. 动态配置对象:原型模式可以用于创建可配置的对象实例。通过使用原型模式,可以先创建一个默认配置的对象实例,然后根据需要进行自定义配置,从而快速创建出符合不同需求的对象。
  4. 保护对象的不变性:有时候,需要确保对象的状态不会被修改。通过使用原型模式,可以在需要修改对象的状态时,复制一个对象作为副本,并对副本进行修改,而原始对象保持不变。这样可以避免对原始对象的意外修改。
  5. 快速生成大量相似对象:在某些场景下,需要大量相似的对象,但每个对象的属性稍有不同。通过使用原型模式,可以先创建一个原型对象,然后根据需要复制多个副本,并根据需求修改副本的属性,从而快速生成大量相似的对象。