原型模式

412 阅读7分钟

什么是原型模式

原型模式(Prototype Pattern)是指创建重复的对象,同时又能保证性能。它属于创建型模式,提供了一种快速创建对象的方法,而不必经过完整的构造过程。通常,在创建新的对象时,我们使用"new"关键字来实例化对象。但是,在某些情况下,该方法可能会因为创建成本高昂,时间长等原因,无法满足需求。此时,原型模式就派上用场了。它通过克隆原型对象来创建新的对象,从而避免了"new"关键字带来的问题。

原型模式的实现方式

原型模式的核心思想是:将一个原型对象作为被克隆的对象,在需要新对象时通过克隆原型对象获取新对象。Java语言中,原型模式的实现主要依靠Object类的clone()方法进行实现。

下面是一个简单的示例代码:

public abstract class Prototype implements Cloneable {

   public abstract void print();

   @Override
   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }

}

public class ConcretePrototype extends Prototype {

   private String name;

   public ConcretePrototype(String name) {
      this.name = name;
   }

   @Override
   public void print() {
      System.out.println("ConcretePrototype Name:" + name);
   }
}

public class Client {

   public static void main(String[] args) {
      ConcretePrototype prototype1 = new ConcretePrototype("prototype1");
      ConcretePrototype prototype2 = (ConcretePrototype) prototype1.clone();
      System.out.println("Prototype1 HashCode:" + prototype1.hashCode());
      System.out.println("Prototype2 HashCode:" + prototype2.hashCode());
      prototype2.print();
   }
}

在上面的示例代码中,我们定义了一个抽象类Prototype作为原型类,并在其中定义了一个抽象方法print()和一个clone()方法。具体的实现类ConcretePrototype继承自原型类,并重写了print()方法。Client类则用来测试原型模式的效果。

原型模式的优缺点

原型模式的优点包括:

  • 提高了系统的性能。 在创建新对象时,原型模式通过克隆原有对象的方式避免了大量的重复构造工作。这样可以大大提高系统的性能和运行速度。
  • 简化了对象的创建过程。 相比于其他创建型模式,原型模式的对象创建过程非常简单,只需要通过克隆原型对象即可获得新的对象,无需过多的构造工作。
  • 可以动态地增加或减少产品类。 原型模式可以动态地增加或减少产品类,使得系统更加灵活。

其缺点包括:

  • 需要额外的注意事项。 在使用原型模式时,需要注意克隆对象的深度和浅度,避免出现意外情况。
  • 克隆出来的对象不会去执行构造函数。 使用原型模式克隆出来的新对象并不会执行构造函数,这在某些情况下可能会导致问题。

原型模式在Java中的应用

原型模式在Java中的应用非常广泛。例如,Java中的java.util.Date就是一个典型的原型模式。另外,在实际开发中,我们也可以把一些常用的对象作为原型,根据需求进行克隆来提高效率。

下面给出一个具体的例子:假设我们要设计一个类库,其中包含许多图形对象(如圆、矩形、三角形等)。由于这些图形对象都具有相似的属性和方法,因此我们可以使用原型模式来实现这个类库。

首先,我们定义一个图形接口:

public interface Shape extends Cloneable {
   void draw();
}

然后,我们分别实现圆、矩形、三角形等图形类,并实现clone()方法:

public class Circle implements Shape {

   public Circle(){
      System.out.println("Circle Constructor Called!");
   }

   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }

   @Override
   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}

public class Rectangle implements Shape {

   public Rectangle(){
      System.out.println("Rectangle Constructor Called!");
   }

   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }

   @Override
   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}

public class Triangle implements Shape {

   public Triangle(){
      System.out.println("Triangle Constructor Called!");
   }

   @Override
   public void draw() {
      System.out.println("Inside Triangle::draw() method.");
   }

   @Override
   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}

最后,我们定义一个ShapeCache类来缓存这些图形对象:

import java.util.HashMap;
import java.util.Map;

public class ShapeCache {

   private static Map<String, Shape> shapeMap = new HashMap<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.setId("1");
      shapeMap.put(circle.getId(),circle);

      Rectangle rectangle = new Rectangle();
      rectangle.setId("2");
      shapeMap.put(rectangle.getId(),rectangle);

      Triangle triangle = new Triangle();
      triangle.setId("3");
      shapeMap.put(triangle.getId(),triangle);
   }
}

在上面的代码中,我们将不同类型的图形对象存储在一个HashMap中,并通过getShape()方法获取它们的克隆对象。loadCache()方法用来初始化这些图形对象。

下面是测试代码:

public class Client {

   public static void main(String[] args) {
      ShapeCache.loadCache();

      Shape clonedShape1 = (Shape) ShapeCache.getShape("1");
      System.out.println("Shape : " + clonedShape1.getType());		

      Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
      System.out.println("Shape : " + clonedShape2.getType());

      Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
      System.out.println("Shape : " + clonedShape3.getType());
   }
}

在上面的代码中,我们通过getShape()方法获取了三种不同类型的图形对象,并打印出它们的类型。

如何在项目中使用原型模式

在实际项目中,我们可以使用原型模式来解决以下问题:

  • 创建新对象的过程比较复杂,需要耗费很多时间和资源。
  • 需要创建大量相似或完全相同的对象。

例如,在游戏开发中,经常需要创建大量的怪物、宝箱等对象。使用原型模式可以避免重复构造这些对象,提高游戏运行效率。另外,在一些需要频繁修改或调整的系统中,原型模式也可以提高系统的灵活性。

原型模式的变形

除了普通的原型模式,还有一些基于原型模式的变形模式,如浅拷贝、深拷贝等。其中,浅拷贝只会复制对象的基本属性,而不会复制嵌套对象;深拷贝则会复制整个对象的所有属性,包括嵌套对象和数组等。以下是一个简单的示例代码:

public class Person implements Cloneable {
   private String name;
   private Address address;

   public Person(String name, Address address) {
      this.name = name;
      this.address = address;
   }

   public void setAddress(String street, String city, String state) {
      address.setStreet(street);
      address.setCity(city);
      address.setState(state);
   }

   public void print() {
      System.out.println("Name: " + name + ", Address: " + address);
   }

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

public class Address implements Cloneable {
   private String street;
   private String city;
   private String state;

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

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

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

   public void setState(String state) {
      this.state = state;
   }

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

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

在上面的代码中,我们定义了一个Person类和一个Address类,并实现了它们的clone()方法。Person类包含一个嵌套的Address对象,用来演示浅拷贝和深拷贝的区别。

以下是测试代码:

public class Client {

   public static void main(String[] args) throws CloneNotSupportedException {
      Address address = new Address("Main St", "New York", "NY");
      Person person1 = new Person("John", address);
      person1.print();

      // 浅拷贝
      Person person2 = (Person) person1.clone();
      // 修改person2的地址信息,会影响到person1的地址信息
      person2.setAddress("2nd Ave", "Chicago", "IL");
      person1.print();
      person2.print();

      // 深拷贝
      Address address2 = (Address) address.clone();
      Person person3 = new Person("Mary", address2);
      // 修改person3的地址信息,不会影响到address和person1的地址信息
      person3.setAddress("3rd St", "Los Angeles", "CA");
      person1.print();
      person3.print();
   }
}

在上面的代码中,我们首先创建了一个Person对象,并打印出它的详细信息。然后,使用浅拷贝和深拷贝分别复制了这个Person对象,并修改了复制后对象的地址信息。最后,打印出所有对象的详细信息。

总结

原型模式是一种简单而实用的创建型模式,它可以帮助我们快速创建大量相似的对象,提高系统的性能和灵活性。通过基于已有对象进行克隆,可以避免重复构造对象的过程,同时还可以简化对象的创建过程。

在使用原型模式时,需要注意几点:

  • 原型对象必须实现Cloneable接口,否则无法进行克隆。
  • 克隆方法clone()应该被重写,并且需要调用父类的clone()方法。
  • 在使用克隆对象时,需要注意深拷贝和浅拷贝的区别。

除了普通的原型模式,还有一些变形模式,如浅拷贝、深拷贝等,可以根据具体需求选择合适的模式。

最后,需要注意的是,在Java中,克隆对象并不总是比直接创建对象更快。如果对象的构造过程很简单,并且只需要创建少量对象,那么直接创建对象可能更加简单和高效。