设计模式之原型模式

引言——游戏士兵军团与原型模式

此时你是一名投身于 RPG 游戏开发的大帅比,此刻正面临着这样一个关键场景:在游戏的虚拟世界里,成群结队的敌人士兵如潮水般向玩家涌来。他们身披铠甲,手持各式武器,乍看之下极为相似,可仔细观察,便能发现其中的微妙差异 —— 有的士兵铠甲颜色略有不同,有的配备的武器种类别具一格,甚至还有些士兵的移动速度明显更快。

身为游戏开发者,该如何高效实现这些士兵的生成呢?倘若每次都要从零开始,为每一个士兵单独创建模型、逐个赋予属性,这不仅会让开发成本直线飙升,还极有可能引发内存和性能方面的诸多问题。那么,有没有一种巧妙的解决办法呢?不妨了解一下运用 **“原型模式” 来打造士兵军团 **。

概述

原型模式是一种创建型设计模式,它允许通过克隆(拷贝)现有对象来创建新对象,而不是通过实例化类。这种模式适用于创建对象成本较高或者复杂的场景,通过克隆操作可以快速生成新对象,同时避免重新初始化的开销。

原型模式定义为:指定一个用来创建对象的原型接口,并通过拷贝这个原型来生成新的对象

原型模式的相关角色

  1. 抽象原型(Prototype):这是一个抽象类或接口,定义了克隆方法,所有具体原型类都需要实现这个方法。
  2. 具体原型(Concrete Prototype):实现了抽象原型接口的具体类,它包含了对象的实际属性和状态,并且实现了克隆方法来复制自身。
  3. 客户端(Client):使用原型模式的类,它通过调用原型对象的克隆方法来创建新对象。

工作流程

1. 定义抽象原型接口或抽象类:**抽象原型(Prototype)**定义了克隆方法的接口,该方法用于创建自身的副本。这一步为具体原型类提供了统一的克隆操作规范,使得客户端能够以相同的方式调用不同具体原型的克隆方法。

2. 创建具体原型类:**具体原型(Concrete Prototype)**实现了抽象原型接口,包含了对象的具体属性和状态,并且实现了克隆方法。在克隆方法中,通常会创建一个新的对象,并将自身的属性值复制到新对象中。

3. 客户端获取原型对象:**客户端(Client)**需要获取一个原型对象作为模板。这个原型对象可以通过多种方式获得,例如通过工厂类创建、从配置文件中读取等。

4. 客户端调用克隆方法:客户端通过调用原型对象的克隆方法来创建新对象。克隆方法会返回一个新的对象,该对象的属性值与原型对象相同。

5. 使用克隆对象:客户端可以像使用普通对象一样使用克隆对象,对其进行操作或修改。由于克隆对象是独立于原型对象的,对克隆对象的修改不会影响原型对象,反之亦然。

示例

下面,我们使用引言中的小例子来实现原型模式。

UML图

在这里插入图片描述

C++实现

#include <iostream>
#include <memory>
#include <string>
#include <vector>

// 抽象原型类
class SoldierPrototype {
public:
    // 定义克隆接口,返回智能指针
    [[nodiscard]] virtual std::unique_ptr<SoldierPrototype> clone() const = 0;

    // 定义展示信息的虚函数
    virtual void display() const = 0;

    // 虚析构函数
    virtual ~SoldierPrototype() noexcept = default;
};

// 具体原型类:普通士兵
class NormalSoldier : public SoldierPrototype {
private:
    std::string armorColor;
    std::string weapon;
    int moveSpeed;

public:
    // 构造函数
    NormalSoldier(const std::string &color, const std::string &weaponType, int speed) noexcept
            : armorColor(color), weapon(weaponType), moveSpeed(speed) {}

    // 克隆方法,返回对象的深拷贝
    [[nodiscard]] std::unique_ptr<SoldierPrototype> clone() const override {
        return std::make_unique<NormalSoldier>(*this);
    }

    // 展示士兵信息
    void display() const override {
        std::cout << "Normal Soldier - Armor Color: " << armorColor
                  << ", Weapon: " << weapon << ", Move Speed: " << moveSpeed
                  << std::endl;
    }
};

// 客户端类
class SoldierFactory {
private:
    // 使用智能指针管理原型士兵
    std::vector<std::unique_ptr<SoldierPrototype>> prototypes;

public:
    // 添加原型士兵
    void addPrototype(std::unique_ptr<SoldierPrototype> prototype) noexcept {
        prototypes.push_back(std::move(prototype));
    }

    // 生成士兵军团
    [[nodiscard]] std::vector<std::unique_ptr<SoldierPrototype>> generateArmy(int numSoldiers) const {
        std::vector<std::unique_ptr<SoldierPrototype>> army;

        if (prototypes.empty()) {
            std::cerr << "No soldier prototypes available! Returning an empty army.\n";
            return army; // 如果没有原型,就返回空军团
        }

        for (int i = 0; i < numSoldiers; ++i) {
            int index = i % prototypes.size();
            army.push_back(prototypes[index]->clone());
        }

        return army;
    }
};

int main() {
    // 创建士兵工厂
    SoldierFactory factory;

    // 添加原型士兵到工厂
    factory.addPrototype(std::make_unique<NormalSoldier>("Red", "Sword", 5));
    factory.addPrototype(std::make_unique<NormalSoldier>("Blue", "Axe", 6));

    // 生成士兵军团
    auto army = factory.generateArmy(5);

    // 显示每个士兵的信息
    for (const auto &soldier : army) {
        soldier->display();
    }

    return 0;
}


Java实现

import java.util.ArrayList;
import java.util.List;

// 抽象原型类
abstract class SoldierPrototype implements Cloneable {
    @Override
    protected abstract SoldierPrototype clone();

    public abstract void display();
}

// 具体原型类:普通士兵
class NormalSoldier extends SoldierPrototype {
    protected String armorColor;
    protected String weapon;
    protected int moveSpeed;

    // protected构造函数,避免直接实例化
    protected NormalSoldier(String armorColor, String weapon, int moveSpeed) {
        this.armorColor = armorColor;
        this.weapon = weapon;
        this.moveSpeed = moveSpeed;
    }

    @Override
    protected NormalSoldier clone() {
        return new NormalSoldier(this.armorColor, this.weapon, this.moveSpeed);
    }

    @Override
    public void display() {
        System.out.println("Normal Soldier - Armor Color: " + armorColor +
                ", Weapon: " + weapon +
                ", Move Speed: " + moveSpeed);
    }
}

// 客户端类:士兵工厂
class SoldierFactory {
    private final List<SoldierPrototype> prototypes = new ArrayList<>();

    public void addPrototype(SoldierPrototype prototype) {
        prototypes.add(prototype);
    }

    public List<SoldierPrototype> generateArmy(int numSoldiers) {
        List<SoldierPrototype> army = new ArrayList<>();
        
        if (prototypes.isEmpty()) {
            System.out.println("No soldier prototypes available!");
            return army; // 如果没有原型,则返回空军团
        }

        // 如果请求的士兵数量超出原型数量,给出警告
        if (numSoldiers > prototypes.size()) {
            System.out.println("Warning: Requested number of soldiers exceeds available prototypes. Repeating prototypes.");
        }

        for (int i = 0; i < numSoldiers; i++) {
            int index = i % prototypes.size();
            army.add(prototypes.get(index).clone());
        }

        return army;
    }
}

// 测试类
public class PrototypePatternDemo {
    public static void main(String[] args) {
        SoldierFactory factory = new SoldierFactory();

        // 添加原型士兵
        factory.addPrototype(new NormalSoldier("Red", "Sword", 5));
        factory.addPrototype(new NormalSoldier("Blue", "Axe", 6));

        // 生成士兵军团
        List<SoldierPrototype> army = factory.generateArmy(5);

        // 显示士兵信息
        for (SoldierPrototype soldier : army) {
            soldier.display();
        }
    }
}


代码解释

  1. 抽象原型类SoldierPrototype:定义了两个纯虚函数clone()display(),所有具体原型类都需要实现这两个方法。
  2. 具体原型类NormalSoldier:继承自SoldierPrototype,实现了clone()方法,用于复制自身,以及display()方法,用于显示士兵的信息。
  3. 客户端类SoldierFactory:负责管理原型士兵,并通过generateArmy()方法生成士兵军团。在生成军团时,通过克隆原型士兵来创建新的士兵对象。
  4. 主函数main():创建了两个原型士兵,并将它们添加到士兵工厂中。然后调用generateArmy()方法生成一个包含 5 个士兵的军团,并显示每个士兵的信息。最后释放所有克隆士兵的内存。

原型模式的优缺点

相信此时你已经对原型模式有了一定的了解,下面对原型模式的优缺点加以说明。

优点

1. 提高对象创建效率

当创建对象的过程较为复杂,例如需要进行大量的初始化操作、从数据库或网络获取数据进行填充等,使用原型模式通过克隆已有对象可以显著提高创建新对象的效率。因为克隆操作通常比重新创建并初始化一个全新对象要快得多,尤其是在需要创建大量相似对象的场景中,优势更为明显。

示例:在游戏开发中,创建一个具有复杂属性和状态的角色对象可能需要加载纹理、初始化技能、设置初始位置等一系列操作。如果要创建多个相同或相似的角色,使用原型模式克隆一个已初始化好的角色原型,能避免重复这些复杂操作,快速得到新的角色对象。

2. 简化对象创建过程

原型模式将对象的创建逻辑封装在原型对象的克隆方法中,客户端只需要调用克隆方法就能得到新对象,无需关心对象创建的具体细节。这使得对象的创建过程更加简洁,减少了客户端代码与对象创建逻辑之间的耦合。

示例:在一个图形绘制系统中,创建不同类型的图形(如圆形、矩形、三角形等)可能需要不同的参数和初始化步骤。使用原型模式,每种图形可以作为一个原型,客户端只需要克隆相应的原型即可得到新的图形对象,而不需要了解每种图形的具体创建方式。

3. 便于动态添加或删除对象类型

在运行时,可以动态地添加或删除原型对象,从而灵活地改变系统能够创建的对象类型。这为系统的扩展提供了便利,无需修改大量的代码。

示例:在一个插件化的系统中,新的插件可能代表了一种新的对象类型。通过将插件对应的对象作为原型添加到系统中,就可以在运行时动态地创建这些新类型的对象,而不需要对系统的核心代码进行修改。

4. 保护对象状态

克隆操作可以创建一个与原型对象独立的新对象,对克隆对象的修改不会影响到原型对象,反之亦然。这在需要保护对象状态不被意外修改的场景中非常有用。

示例:在一个文档处理系统中,用户可能需要对文档进行多次修改和保存。可以将原始文档作为原型,每次需要修改时克隆一个新的文档对象进行操作,这样即使修改过程中出现错误,也不会影响到原始文档的状态。

缺点

1. 克隆方法实现复杂

对于一些包含复杂引用类型成员变量的对象,实现克隆方法可能会比较复杂。浅克隆只复制对象的基本类型属性和引用,而不复制引用指向的对象,可能会导致多个对象共享同一个引用对象,从而引发数据不一致的问题;深克隆则需要递归地复制所有引用对象,实现起来较为困难,并且可能会带来性能开销。

示例:一个对象包含一个指向另一个复杂对象的引用,如果进行浅克隆,克隆对象和原型对象会共享这个引用,当其中一个对象修改该引用对象的状态时,会影响到另一个对象。而实现深克隆时,需要确保正确地复制该引用对象及其所有子对象,这可能涉及到大量的代码和复杂的逻辑。

2. 管理原型对象的成本

随着系统中原型对象的增多,管理这些原型对象会变得困难。需要确保原型对象的状态是正确的,并且在不需要时及时释放资源,否则可能会导致内存泄漏等问题。

示例:在一个大型的游戏项目中,可能存在数十种甚至上百种不同类型的怪物原型。如果没有合理的管理机制,可能会导致原型对象的状态混乱,或者在游戏运行过程中占用过多的内存资源。

3. 不支持循环引用

当对象之间存在循环引用时,实现深克隆会变得非常困难,甚至可能导致无限递归。在这种情况下,原型模式的使用会受到限制。

示例:对象 A 引用了对象 B,而对象 B 又引用了对象 A。在进行深克隆时,由于需要递归地复制所有引用对象,会陷入无限循环,导致程序崩溃。

注意事项

  • 浅克隆与深克隆的选择
    • 浅克隆:仅复制对象的基本数据类型和对象引用,而不复制引用指向的对象本身。这意味着克隆对象和原型对象可能会共享某些引用对象,对这些共享对象的修改会同时影响两者。因此,当对象中的引用对象不需要独立存在,或者对象的引用结构较为简单时,可以使用浅克隆。例如,一个包含基本数据类型和一些轻量级不可变对象引用的对象。
    • 深克隆:会递归地复制对象的所有引用对象,确保克隆对象和原型对象完全独立。当对象的引用结构复杂,且需要保证克隆对象和原型对象的状态互不影响时,必须使用深克隆。但深克隆的实现通常较为复杂,且可能会带来一定的性能开销。例如,一个包含嵌套对象和集合的复杂对象。
  • 处理复杂对象结构
    • 对于包含复杂引用类型(如集合、数组、自定义对象等)的对象,在实现克隆方法时要确保正确处理这些引用。例如,对于集合类型,需要逐个复制集合中的元素;对于自定义对象,可能需要调用其自身的克隆方法。
    • 注意对象中的静态成员变量,因为静态成员变量属于类而不是对象,克隆操作通常不会影响静态成员变量。
  • 初始化和维护
    • 原型对象需要正确初始化,确保其状态是稳定和可复用的。在系统运行过程中,要避免意外修改原型对象的状态,因为这可能会影响到后续克隆出的对象。
    • 可以使用一个原型管理器来集中管理所有的原型对象,方便原型对象的添加、删除和查找。
  • 资源释放
    • 当原型对象不再使用时,要及时释放其所占用的资源,避免内存泄漏。特别是对于一些包含外部资源(如文件句柄、数据库连接等)的原型对象,需要在合适的时机进行资源清理。
  • 克隆操作的开销
    • 深克隆由于需要递归复制所有引用对象,可能会带来较大的性能开销。在需要频繁进行克隆操作的场景中,要谨慎使用深克隆,或者考虑对性能进行优化,如缓存已经克隆过的对象。
    • 对于一些简单对象,使用浅克隆可以提高克隆操作的效率,但要注意其可能带来的数据共享问题。
  • 内存占用
    • 大量的原型对象和克隆对象可能会占用较多的内存空间。在设计时要考虑内存的使用情况,避免出现内存溢出的问题。可以采用对象池等技术来复用对象,减少内存开销。
  • 避免无限递归
    • 当对象之间存在循环引用时,深克隆可能会导致无限递归。在实现克隆方法时,要检测并处理循环引用的情况,例如使用一个标记来记录已经克隆过的对象,避免重复克隆。

与工厂模式的对比

对比维度原型模式工厂模式
目的将对象创建和使用分离,降低耦合度,提高可维护性与可扩展性,通过复制已有对象提高代码复用性将对象创建和使用分离,降低耦合度,提高可维护性与可扩展性,通过封装创建逻辑提高代码复用性
实现方式基于克隆机制,定义抽象原型接口包含克隆方法,具体原型类实现该接口和方法,客户端调用克隆方法创建新对象基于工厂类,定义工厂类包含创建对象的方法,客户端调用工厂类的创建方法获取新对象
创建对象复杂度适合创建过程复杂且对象相似度高的场景,复制已有对象避免复杂初始化过程,提高创建效率适合创建逻辑复杂但对象差异较大的场景,工厂类集中管理创建逻辑,提高可维护性
对已有对象的依赖依赖已有原型对象,需先有一个或多个原型作为模板不依赖已有对象,根据传入参数或配置信息全新创建对象
灵活性运行时可动态改变对象类型和属性,通过克隆不同原型创建不同对象相对固定,修改创建逻辑较复杂,但可通过继承和多态实现一定灵活性,如抽象工厂模式可创建一系列相关对象

应用场景

原型模式是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需了解对象的具体创建过程。以下是原型模式的一些常见应用场景:

  • 对象创建成本较高时
    • 数据库连接对象:在数据库操作中,建立数据库连接通常需要配置参数、加载驱动程序、进行网络连接等复杂操作,成本较高。使用原型模式,可以在系统启动时创建一个数据库连接对象作为原型,当需要新的连接时,通过克隆原型来快速获取新的连接对象,避免了重复的连接创建过程,提高了系统性能。
    • 复杂图形对象:在图形编辑软件中,创建复杂的图形对象,如包含大量控制点和属性的曲线、多边形等,可能涉及到复杂的计算和初始化操作。将已经创建好的复杂图形对象作为原型,通过克隆来创建相同或类似的图形,可以大大减少创建新图形的时间和计算资源。
  • 对象初始化过程复杂时
    • 游戏角色创建:在游戏开发中,游戏角色通常具有复杂的属性和行为,如不同的技能、装备、状态等。使用原型模式,可以预先创建各种类型的角色原型,在游戏运行时,根据需要克隆这些原型来创建新的角色,避免了在创建每个角色时都进行繁琐的初始化操作。
    • 表单对象构建:在Web应用开发中,表单对象可能包含多个字段、验证规则、默认值等复杂的配置信息。通过创建表单原型,在需要生成新的表单时,直接克隆原型,并根据具体需求进行少量的修改,能够提高表单创建的效率,减少出错的可能性。
  • 需要动态创建对象时
    • 配置管理系统:在软件系统的配置管理中,可能需要根据不同的配置文件或用户需求动态创建不同的配置对象。使用原型模式,可以定义一些基本的配置原型,然后在运行时根据具体的配置信息克隆并修改原型,生成符合要求的配置对象,提高了系统的灵活性和可扩展性。
    • 报表生成系统:在报表生成工具中,用户可能根据不同的需求选择不同类型的报表模板,如柱状图报表、折线图报表、表格报表等。可以将各种报表模板定义为原型,当用户选择某种报表类型时,通过克隆相应的原型并填充具体数据来生成报表,实现了报表的动态创建。
  • 需要复制对象状态时
    • 多版本对象管理:在一些文档编辑软件或版本控制系统中,需要保存对象的多个版本,并且每个版本之间可能只有少量的差异。使用原型模式,可以在每次保存版本时,克隆当前对象,并对克隆后的对象进行修改,这样可以高效地保存和管理对象的不同状态,同时避免了大量的数据重复存储。
    • 撤销/重做功能实现:在许多软件应用中,需要实现撤销和重做功能,这就需要保存对象的历史状态。通过将对象在不同操作步骤时的状态作为原型进行克隆和保存,当用户执行撤销或重做操作时,就可以通过恢复相应的原型来实现对象状态的回退或前进。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值