设计模式-原型模式(Prototype)

272 阅读3分钟

原型模式(Prototype)

Prototype:原型模式是一种创建型设计模式,使你能够复制原有的对象,而又无需使代码依赖它所需的类;用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

/**
 * 原型模式
 *
 * @author CAI
 * @time 2020/11/7
 */
public class PrototypePattern {
​
    public static void main(String[] args) {
        List<Shape> shapes = new ArrayList<>();
        List<Shape> shapesCopy = new ArrayList<>();
​
        Circle circle = new Circle();
        circle.x = 10;
        circle.y = 20;
        circle.radius = 15;
        circle.color = "red";
        shapes.add(circle);
​
        Circle anotherCircle = (Circle) circle.clone();
        shapes.add(anotherCircle);
​
        Rectangle rectangle = new Rectangle();
        rectangle.width = 10;
        rectangle.height = 20;
        rectangle.color = "blue";
        shapes.add(rectangle);
​
        cloneAndCompare(shapes, shapesCopy);
    }
​
    /**
     * 克隆和比较
     *
     * @param shapes 形状列表
     * @param shapesCopy 克隆形状列表
     */
    private static void cloneAndCompare(List<Shape> shapes, List<Shape> shapesCopy) {
        // 克隆
        for (Shape shape : shapes) {
            shapesCopy.add(shape.clone());
        }
​
        for (int i = 0; i < shapes.size(); i++) {
            // 内存地址是否相同
            if (shapes.get(i) != shapesCopy.get(i)) {
                System.out.println(i + " : 与克隆对象不是同一个对象");
​
                // 对象属性是否相同
                if (shapes.get(i).equals(shapesCopy.get(i))) {
                    System.out.println(i + ": 与克隆对象属性相同");
​
                } else {
                    System.out.println(i + ": But they are not identical (booo!)");
                }
            } else {
                System.out.println(i + ": 与克隆对象属性不同");
            }
        }
    }
}
​
/**
 * 通用形状:原型接口或抽象类
 */
abstract class Shape {
    public int x;
    public int y;
    public String color;
​
    public Shape() { }
​
    public Shape(Shape target) {
        if (target != null) {
            this.x = target.x;
            this.y = target.y;
            this.color = target.color;
        }
    }
​
    protected abstract Shape clone();
​
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Shape)) {
            return false;
        }
​
        Shape shape = (Shape) obj;
        return shape.x == x && shape.y == y && Objects.equals(shape.color, color);
    }
}
​
/**
 * 简单形状:具体原型
 */
class Circle extends Shape {
    public int radius;
​
    public Circle() { }
​
    public Circle(Circle target) {
        super(target);
​
        if (target != null) {
            this.radius = target.radius;
        }
    }
​
    @Override
    public Shape clone() {
        return new Circle(this);
    }
​
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Circle) || !super.equals(obj)) {
            return false;
        }
​
        Circle shape = (Circle) obj;
        return shape.radius == radius;
    }
}
​
/**
 * 另一个形状:具体原型
 */
class Rectangle extends Shape {
    public int width;
    public int height;
​
    public Rectangle() { }
​
    public Rectangle(Rectangle target) {
        super(target);
        if (target != null) {
            this.width = target.width;
            this.height = target.height;
        }
    }
​
    @Override
    public Shape clone() {
        return new Rectangle(this);
    }
​
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Rectangle) || !super.equals(obj)) {
            return false;
        }
​
        Rectangle shape = (Rectangle) obj;
        return shape.width == width && shape.height == height;
    }
}
  1. 形状:原型的接口或抽象类,声明clone();在绝大多数情况下,只会有一个名为clone的方法。
  2. 简单形状/其他形状:原型的具体实现类,实现clone方法。除了将原始对象的数据复制到克隆体中,该方法还需要处理克隆过程中的极端情况。如:对象中涉及引用对象或递归依赖等。

原型注册表

/**
 * 原型注册表
 *
 * @author Viices Cai
 * @time 2022/1/13
 */
public class PrototypeRegedit {
​
    public static void main(String[] args) {
        BundledShapeCache cache = new BundledShapeCache();
​
        Shape shape1 = cache.get("Big green circle");
        Shape shape2 = cache.get("Medium blue rectangle");
        Shape shape3 = cache.get("Medium blue rectangle");
​
        if (shape1 != shape2 && !shape1.equals(shape2)) {
            System.out.println("Big green circle 与 Medium blue rectangle 不是同一个对象.");
​
        } else {
            System.out.println("Big green circle 与 Medium blue rectangle 是同一个对象.");
        }
​
        if (shape2 != shape3) {
            System.out.println("两个 Medium blue rectangle 不是同一个对象.");
​
            if (shape2.equals(shape3)) {
                System.out.println("两个 Medium blue rectangle 属性相同.");
​
            } else {
                System.out.println("两个 Medium blue rectangle 属性不相同.");
            }
​
        } else {
            System.out.println("两个 Medium blue rectangle 是同一个对象.");
        }
    }
}
​
/**
 * 原型工厂
 */
class BundledShapeCache {
    private Map<String, Shape> cache = new HashMap<>();
​
    public BundledShapeCache() {
        Circle circle = new Circle();
        circle.x = 5;
        circle.y = 7;
        circle.radius = 45;
        circle.color = "Green";
​
        Rectangle rectangle = new Rectangle();
        rectangle.x = 6;
        rectangle.y = 9;
        rectangle.width = 8;
        rectangle.height = 10;
        rectangle.color = "Blue";
​
        cache.put("Big green circle", circle);
        cache.put("Medium blue rectangle", rectangle);
    }
​
    public Shape put(String key, Shape shape) {
        cache.put(key, shape);
        return shape;
    }
​
    public Shape get(String key) {
        return cache.get(key).clone();
    }
}
  • 原型注册表:提供一种访问常用原型的简单方法,其中存储了一系列可供随时复制的预生成对象。

    • 最简易的实现是维护一个name -> prototypemap,如需要使用名称之外的条件进行搜索,可以在此基础上进行改造。

使用场景

  1. 经常面临某些结构复杂的对象的创建工作,这些对象经常面临着剧烈的变化,但是拥有比较稳定一致的接口可以使用原型模式。

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

    • 通常出现于在需要处理对第三方的接口代码传递过来的对象时;原型模式为调用者提供一个通用的接口,调用者可以通过这个接口与所有实现了克隆的对象进行交互,也使得调用的代码和其克隆的具体对象类独立开。
  3. 如果子类的区别仅在于其对象的初始化方式,可以使用该模式来减少子类的数量。

    • 在原型模式中可以使用一系列预生成的、各种类型的对象作为原型。调用者不必根据需求对子类进行初始化,只需要调用合适的原型并进行克隆。

优缺点

  • 优点:

    • NEW一次,都需要执行一次构造函数,多次执行该操作过于低效。一般在初始化的信息不发生变化的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又提高了性能。
    • 不用重新初始化对象,而是动态地获得对象运行时的状态。
    • 克隆对象时,无需与所属的具体类相耦合。
    • 更方便地生成复杂对象。
    • 可以用继承之外的方式来处理复杂对象的不同配置。
  • 缺点:

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

在 JAVA 中的运用

  • Java中的Cloneable接口就是立即可以的原型模式。

    • 任何类都可以通过该接口来实现可被克隆的性质。
    • 原型可以简单的通过clone()copy()进行辨别。

引出概念

  • 浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
  • 深拷贝:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。