授课语音

原型模式及其在 Java 中的实现

原型模式(Prototype Pattern)是一种创建型设计模式,旨在通过复制已有的实例来创建新的对象,而不是通过实例化一个新对象。该模式通过提供一个原型实例,并用该实例来创建新的对象,从而节省了创建对象的成本,尤其是在需要大量相似对象的场景下。


1. 原型模式的定义

原型模式的核心思想是:通过复制一个已有的对象来创建新的对象。它提供了一个原型接口,允许对象克隆自己,从而避免了重新创建对象的过程。

优点

  • 减少对象创建的开销,特别是在需要创建大量相似对象时。
  • 可以在运行时动态配置对象属性。
  • 原型模式可以实现深拷贝与浅拷贝的灵活选择。

缺点

  • 对象的拷贝可能会较为复杂,特别是在需要深拷贝的情况下。
  • 需要显式地定义克隆方法,增加了代码的复杂度。

2. 原型模式的结构

原型模式通常包括以下几个角色:

  1. Prototype(原型接口):定义了克隆自身的接口。
  2. ConcretePrototype(具体原型类):实现了 clone() 方法,通过该方法实现自身的复制。
  3. Client(客户端):通过 Prototype 接口获取一个克隆对象,而不需要关心对象是如何被克隆的。

3. 原型模式的应用场景

原型模式适用于以下几种场景:

  • 对象创建成本较高:通过复制已有的对象来节省新建对象的成本。
  • 复杂的对象需要创建多个副本:通过克隆原型对象来快速创建相似对象。
  • 当需要基于原型实例来创建对象,并且对象的创建过程可能会变得复杂时

4. 在 Java 中实现原型模式

Java 中的 Object 类提供了 clone() 方法,可以用来实现对象的复制。我们可以通过覆盖 clone() 方法来实现原型模式。

4.1 浅拷贝与深拷贝

  • 浅拷贝:创建一个新对象,新对象的成员变量与原对象的成员变量引用相同的内存地址。如果成员变量是引用类型,则修改新对象的引用类型成员变量会影响原对象。
  • 深拷贝:创建一个新对象,并且新对象的成员变量是原对象成员变量的副本,即所有的引用类型成员变量会被重新复制一份,修改新对象的引用类型成员变量不会影响原对象。

5. 代码实现:浅拷贝与深拷贝

我们通过一个简单的例子来演示原型模式的实现,分为浅拷贝和深拷贝两种方式。

5.1 浅拷贝实现

// 原型接口,定义克隆方法
interface Prototype extends Cloneable {
    Prototype clone();  // 复制自身
}

// 具体原型类
class ConcretePrototype implements Prototype {
    private String name;
    private int age;

    // 构造方法
    public ConcretePrototype(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 重写clone方法,实现浅拷贝
    @Override
    public Prototype clone() {
        try {
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{name='" + name + "', age=" + age + "}";
    }

    // Getter & Setter
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

// 客户端代码,演示原型模式的浅拷贝
public class PrototypePatternDemo {
    public static void main(String[] args) {
        ConcretePrototype prototype1 = new ConcretePrototype("Alice", 25);
        // 通过原型创建一个新对象
        ConcretePrototype prototype2 = (ConcretePrototype) prototype1.clone();
        
        // 输出原型对象与克隆对象
        System.out.println("原型对象: " + prototype1);
        System.out.println("克隆对象: " + prototype2);
    }
}

5.2 深拷贝实现

为了演示深拷贝,我们需要确保所有成员变量都能被完全复制。

// 原型接口,定义克隆方法
interface Prototype extends Cloneable {
    Prototype clone();  // 复制自身
}

// 具体原型类
class ConcretePrototype implements Prototype {
    private String name;
    private int age;
    private Address address; // 引用类型成员变量

    // 构造方法
    public ConcretePrototype(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    // 重写clone方法,实现深拷贝
    @Override
    public Prototype clone() {
        try {
            ConcretePrototype clone = (ConcretePrototype) super.clone();
            // 深拷贝引用类型成员变量
            clone.address = new Address(address.getCity(), address.getStreet());
            return clone;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{name='" + name + "', age=" + age + ", address=" + address + "}";
    }

    // Getter & Setter
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

// 地址类,用于深拷贝示例
class Address {
    private String city;
    private String street;

    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }

    @Override
    public String toString() {
        return "Address{city='" + city + "', street='" + street + "'}";
    }

    // Getter & Setter
    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }
}

// 客户端代码,演示原型模式的深拷贝
public class PrototypePatternDemo {
    public static void main(String[] args) {
        Address address = new Address("New York", "5th Avenue");
        ConcretePrototype prototype1 = new ConcretePrototype("Alice", 25, address);

        // 通过原型创建一个新对象
        ConcretePrototype prototype2 = (ConcretePrototype) prototype1.clone();
        
        // 修改克隆对象的地址
        prototype2.getAddress().setCity("Los Angeles");

        // 输出原型对象与克隆对象
        System.out.println("原型对象: " + prototype1);
        System.out.println("克隆对象: " + prototype2);
    }
}

6. 代码注释

  • 原型接口:定义了 clone() 方法,任何类实现该接口后,都可以克隆自身。
  • ConcretePrototype:实现了 Prototype 接口,提供了浅拷贝和深拷贝的实现。在浅拷贝中,clone() 方法直接调用 super.clone(),而在深拷贝中,克隆对象后,还手动克隆了引用类型的成员变量。
  • Address 类:示例中的引用类型,演示如何通过深拷贝解决引用类型的克隆问题。
  • 客户端代码:演示了如何使用原型模式来克隆对象,以及如何修改克隆对象的状态。

7. 复杂度分析

  • 时间复杂度clone() 方法的时间复杂度通常是 O(n),其中 n 是对象的成员变量数量。在深拷贝中,复杂度会更高,因为需要递归复制引用类型的成员变量。
  • 空间复杂度:克隆操作需要占用额外的内存空间,空间复杂度是 O(n),其中 n 是对象的成员变量数量。

8. 总结

原型模式通过提供一个原型对象并通过克隆该对象来创建新的实例,避免了重新创建对象的过程,尤其适用于对象创建成本较高或需要大量相似对象的场景。在 Java 中,我们可以通过 Cloneable 接口和 clone() 方法实现原型模式。

去1:1私密咨询

系列课程: