Skip to content

原型模式

引言

在软件开发中,我们经常需要创建大量相似对象的实例。通常情况下,我们会使用new关键字和构造函数来完成这个任务。然而,当对象的创建过程比较复杂或者需要大量资源时,这种方式可能会导致性能问题。原型模式(Prototype Pattern)就是为了解决这类问题而诞生的,它通过复制现有对象的方式来创建新对象,而不是通过实例化类。

诞生背景

原型模式最早由Erich Gamma等人在《设计模式:可复用面向对象软件的基础》一书中提出。它是设计模式中较为基础的一种模式,适用于需要频繁创建相似对象的场景。随着软件开发实践的发展,开发者们发现有时通过复制已有对象来创建新对象比从头开始创建更加高效,原型模式正是基于这一思想提出的。

演进过程

  • GoF设计模式提出:原型模式作为《设计模式:可复用面向对象软件的基础》一书中提出的23种基础设计模式之一,由Erich Gamma等人正式命名并定义。
  • 语言层面的支持:随着Java、PHP等面向对象语言的发展,原型模式逐渐被语言特性支持,如Java的Cloneable接口和clone()方法,PHP的__clone魔术方法,简化了原型模式的实现。
  • 与工厂模式结合使用:在实际开发中,原型模式常与工厂模式结合,用于动态创建对象副本,避免重复初始化的开销。
  • 现代框架中的应用:现代框架如Spring(Java)和Symfony(PHP)通过原型作用域(Prototype Scope)实现了类似原型模式的功能,使得对象克隆更加灵活和配置化。

核心概念

原型模式的核心在于定义一个原型接口,该接口要求实现一个克隆方法,用于创建并返回当前对象的副本。具体来说,它包含以下几个关键角色:

  • 抽象原型(Abstract Prototype):定义克隆方法的接口。
  • 具体原型(Concrete Prototype):实现克隆方法的具体类。
  • 客户端(Client):使用原型对象来创建新对象的类或方法。

通用实现

java 实现

java
// 抽象原型
public interface Prototype extends Cloneable {
    Prototype clone();
    void setProperty(String property);
    String getProperty();
}

// 具体原型
public class ConcretePrototype implements Prototype, Cloneable {
    private String property;

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

    @Override
    public Prototype clone() {
        try {
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Clone not supported", e);
        }
    }

    @Override
    public void setProperty(String property) {
        this.property = property;
    }

    @Override
    public String getProperty() {
        return property;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "property='" + property + '\'' +
                '}';
    }
}

// 客户端代码
public class Client {
    public static void main(String[] args) {
        // 创建原型对象
        Prototype prototype = new ConcretePrototype("Original Property");
        System.out.println("Original: " + prototype);

        // 克隆原型对象
        Prototype clone = prototype.clone();
        System.out.println("Clone before change: " + clone);

        // 修改克隆对象属性
        clone.setProperty("Modified Property");
        System.out.println("Clone after change: " + clone);
    }
}

php 实现

php
// 具体原型
class ConcretePrototype implements \Serializable {
    private $property;

    public function __construct($property) {
        $this->property = $property;
    }

    public function clone() {
        return new self($this->property);
    }

    public function setProperty($property) {
        $this->property = $property;
    }

    public function getProperty() {
        return $this->property;
    }

    public function serialize() {
        return serialize($this->property);
    }

    public function unserialize($serialized) {
        $this->property = unserialize($serialized);
    }

    public function __toString() {
        return "ConcretePrototype{property='$this->property'}";
    }
}

// 客户端代码
$prototype = new ConcretePrototype("Original Property");
echo "Original: " . $prototype . PHP_EOL;

// 克隆原型对象
$clone = $prototype->clone();
echo "Clone before change: " . $clone . PHP_EOL;

// 修改克隆对象属性
$clone->setProperty("Modified Property");
echo "Clone after change: " . $clone . PHP_EOL;

应用场景

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

  • 当对象的创建成本较大时(如需要复杂计算或网络请求)。
  • 当需要创建大量相似对象时。
  • 当系统需要独立于对象的创建、组合和表示时。
  • 当对象的类名不确定或需要动态改变时。

案例

一个典型的案例是图形编辑器中的形状复制功能。编辑器需要支持复制不同类型的形状(如圆形、矩形、多边形),每种形状都有自己的属性(如位置、颜色、大小等)。使用原型模式,我们可以让每种形状都实现克隆方法,从而简化形状复制的实现。

java 实现

java
// 抽象原型
public interface Shape extends Cloneable {
    Shape clone();
    void draw();
}

// 圆形实现
public class Circle implements Shape, Cloneable {
    private int radius;
    private int x;
    private int y;
    private String color;

    public Circle(int radius, int x, int y, String color) {
        this.radius = radius;
        this.x = x;
        this.y = y;
        this.color = color;
    }

    @Override
    public Shape clone() {
        try {
            return (Shape) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Clone not supported", e);
        }
    }

    @Override
    public void draw() {
        System.out.println("Circle: radius=" + radius + ", x=" + x + ", y=" + y + ", color=" + color);
    }
}

// 矩形实现
public class Rectangle implements Shape, Cloneable {
    private int width;
    private int height;
    private int x;
    private int y;
    private String color;

    public Rectangle(int width, int height, int x, int y, String color) {
        this.width = width;
        this.height = height;
        this.x = x;
        this.y = y;
        this.color = color;
    }

    @Override
    public Shape clone() {
        try {
            return (Shape) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("Clone not supported", e);
        }
    }

    @Override
    public void draw() {
        System.out.println("Rectangle: width=" + width + ", height=" + height + ", x=" + x + ", y=" + y + ", color=" + color);
    }
}

// 客户端代码
public class GraphicsEditor {
    public static void main(String[] args) {
        // 创建原型对象
        Shape circlePrototype = new Circle(10, 100, 100, "red");
        Shape rectanglePrototype = new Rectangle(50, 30, 200, 200, "blue");

        // 克隆并绘制形状
        Shape circle1 = circlePrototype.clone();
        circle1.draw();

        Shape circle2 = circlePrototype.clone();
        circle2.draw();

        Shape rectangle1 = rectanglePrototype.clone();
        rectangle1.draw();
    }
}

php 实现

php
// 抽象原型
abstract class Shape implements \Serializable {
    abstract public function draw();
    abstract public function clone();
}

// 圆形实现
class Circle extends Shape {
    private $radius;
    private $x;
    private $y;
    private $color;

    public function __construct(int $radius, int $x, int $y, string $color) {
        $this->radius = $radius;
        $this->x = $x;
        $this->y = $y;
        $this->color = $color;
    }

    public function clone() {
        return new Circle($this->radius, $this->x, $this->y, $this->color);
    }

    public function draw() {
        echo "Circle: radius={$this->radius}, x={$this->x}, y={$this->y}, color={$this->color}\n";
    }

    public function serialize() {
        return serialize([$this->radius, $this->x, $this->y, $this->color]);
    }

    public function unserialize($serialized) {
        list($this->radius, $this->x, $this->y, $this->color) = unserialize($serialized);
    }
}

// 矩形实现
class Rectangle extends Shape {
    private $width;
    private $height;
    private $x;
    private $y;
    private $color;

    public function __construct(int $width, int $height, int $x, int $y, string $color) {
        $this->width = $width;
        $this->height = $height;
        $this->x = $x;
        $this->y = $y;
        $this->color = $color;
    }

    public function clone() {
        return new Rectangle($this->width, $this->height, $this->x, $this->y, $this->color);
    }

    public function draw() {
        echo "Rectangle: width={$this->width}, height={$this->height}, x={$this->x}, y={$this->y}, color={$this->color}\n";
    }

    public function serialize() {
        return serialize([$this->width, $this->height, $this->x, $this->y, $this->color]);
    }

    public function unserialize($serialized) {
        list($this->width, $this->height, $this->x, $this->y, $this->color) = unserialize($serialized);
    }
}

// 客户端代码
$circlePrototype = new Circle(10, 100, 100, "red");
$rectanglePrototype = new Rectangle(50, 30, 200, 200, "blue");

// 克隆并绘制形状
$circle1 = $circlePrototype->clone();
$circle1->draw();

$circle2 = $circlePrototype->clone();
$circle2->draw();

$rectangle1 = $rectanglePrototype->clone();
$rectangle1->draw();

优点

  • 简化对象创建:原型模式允许通过复制现有对象来创建新对象,避免了复杂的初始化过程。
  • 提高性能:对于创建成本较大的对象,克隆通常比从头创建更快。
  • 解耦:客户端代码不需要知道具体类的名称,只需要知道能够克隆的接口。
  • 增加系统灵活性:可以在运行时动态添加或删除原型。

缺点

  • 拷贝复杂对象困难:如果对象包含复杂结构或循环引用,实现深拷贝会很困难。
  • 安全性问题:克隆方法可能暴露对象的内部实现细节。
  • 违背封装性:为了实现克隆功能,可能需要暴露对象的内部状态。

扩展

  • 深拷贝与浅拷贝:可以通过序列化或手动复制来实现深拷贝,避免对象间的数据共享问题。
  • 结合其他设计模式
    • 工厂模式:原型模式可以与工厂模式结合,通过注册原型来创建对象。
    • 单例模式:可以确保一个原型类只有一个实例存在,从而节省系统资源。
    • 建造者模式:可以用于创建复杂的原型对象。
  • 原型管理器:可以实现一个原型管理器类,用于注册、查找和克隆原型对象。
  • 依赖注入:原型模式可以与依赖注入框架结合使用,通过框架管理原型和克隆对象。

模式协作

  1. 与工厂模式结合
  • 可以使用工厂模式来管理原型的创建和注册。
  • 原型模式可以作为工厂模式的一种补充,提供更灵活的对象创建方式。
  1. 与单例模式结合
  • 原型管理器可以设计为单例,确保整个系统中使用的是同一个管理器实例。
  1. 与建造者模式结合
  • 建造者模式负责复杂原型对象的构建,而原型模式负责复制这些对象。
  1. 与适配器模式结合
  • 可以在已有对象的基础上,适配新的原型接口,从而复用已有代码。

延伸思考

  1. 深拷贝与浅拷贝的选择
  • 对于包含引用类型属性的对象,应该实现深拷贝,避免数据共享问题。
  • 实现深拷贝可以通过序列化、手动复制或使用拷贝构造函数等方式。
  1. 性能与可维护性权衡
  • 虽然原型模式提升了对象创建的性能,但实现克隆方法可能会增加代码复杂度。
  1. 现代框架中的替代方案
  • 依赖注入(DI)框架可以自动管理对象的创建和复制。
  • 使用Spring(Java)或Symfony(PHP)等框架时,可以通过配置实现类似原型模式的功能。
  1. 函数式编程的影响
  • 在函数式编程中,不可变数据结构天然支持安全的克隆操作。
  • 可以通过不可变性来简化原型模式的实现。

总结

原型模式是一种简单而强大的设计模式,适用于需要频繁创建相似对象的场景。通过复制现有对象来创建新对象,可以避免复杂的初始化过程,提高性能。尽管实现深拷贝可能会有挑战,但对于需要大量相似对象的系统来说,这种模式能够带来显著的好处。原型模式可以与其他设计模式灵活结合,实现更复杂和灵活的对象创建机制。