设计模式之原型模式

问题背景

在开发一个图形设计软件时,我们面临一个常见的需求:用户需要频繁地创建和编辑各种图形,如圆形、矩形和多边形。其中,许多图形元素在属性上非常相似,比如颜色、大小或样式可能只有细微的差别。用户希望能够快速复制一个已有图形,然后对其进行小的修改,而不是每次都从零开始创建。这不仅能节省时间,还可以提高整体的工作效率。

问题分析

原型模式通过允许对象复制自身来支持这种需求,这是一种创建型设计模式,特别适合于创建成本较高的情况。在图形设计软件中,使用原型模式可以让用户快速复制和自定义复杂的图形元素,而无需知道对象的具体类。这种方式可以极大地简化创建过程,特别是在涉及复杂对象(需要复杂的初始化参数)的场景中。

代码部分

定义抽象原型和具体原型

#include 
#include 
#include 
#include 

// 抽象原型类 - Shape
class Shape {
public:
    // 克隆函数,用于复制对象
    virtual std::shared_ptr<Shape> clone() const = 0;
    // 绘制函数,纯虚函数需要在子类中实现
    virtual void draw() const = 0;
    // 虚析构函数,确保在销毁对象时调用派生类的析构函数
    virtual ~Shape() {}
};

// 具体原型类 - Circle
class Circle : public Shape {
private:
    std::string color; // 圆的颜色
    int radius; // 圆的半径

public:
    // 构造函数,初始化圆的颜色和半径
    Circle(const std::string& color, int radius) : color(color), radius(radius) {}
    
    // 克隆函数,返回一个当前对象的副本
    std::shared_ptr<Shape> clone() const override {
        return std::make_shared<Circle>(*this); // 使用当前对象的拷贝构造函数创建副本
    }
    
    // 绘制函数,输出绘制圆的信息
    void draw() const override {
        std::cout << "Drawing a " << color << " circle with radius " << radius << std::endl;
    }
};
// 具体原型类 - Rectangle
class Rectangle : public Shape {
private:
    std::string color; // 矩形的颜色
    int width; // 矩形的宽度
    int height; // 矩形的高度

public:
    // 构造函数,初始化矩形的颜色、宽度和高度
    Rectangle(const std::string& color, int width, int height) : color(color), width(width), height(height) {}
    
    // 克隆函数,返回一个当前对象的副本
    std::shared_ptr<Shape> clone() const override {
        return std::make_shared<Rectangle>(*this); // 使用当前对象的拷贝构造函数创建副本
    }
    
    // 绘制函数,输出绘制矩形的信息
    void draw() const override {
        std::cout << "Drawing a " << color << " rectangle with width " << width << " and height " << height << std::endl;
    }
};

实现原型管理器

#include 
#include 
#include 

// 原型管理器类
class PrototypeManager {
private:
    std::unordered_map<std::string, std::shared_ptr<Shape>> prototypes; // 存储原型对象的哈希表

public:
    // 注册原型对象
    void registerPrototype(const std::string& key, std::shared_ptr<Shape> prototype) {
        prototypes[key] = prototype; // 将原型对象存储到哈希表中
    }

    // 获取原型对象
    std::shared_ptr<Shape> getPrototype(const std::string& key) {
        // 通过键值查找原型对象,并调用其克隆函数返回一个副本
        return prototypes[key]->clone();
    }
};

使用原型模式在客户端代码中创建和修改图形对象

int main() {
    // 创建原型管理器对象
    PrototypeManager manager;
    
    // 注册原型对象:大红色圆形和小蓝色矩形
    manager.registerPrototype("large red circle", std::make_shared<Circle>("red", 10));
    manager.registerPrototype("small blue rectangle", std::make_shared<Rectangle>("blue", 5, 3));

    // 获取原型对象的副本
    auto circle = manager.getPrototype("large red circle"); // 获取大红色圆形的副本
    auto rectangle = manager.getPrototype("small blue rectangle"); // 获取小蓝色矩形的副本

    // 绘制图形
    circle->draw(); // 绘制大红色圆形
    rectangle->draw(); // 绘制小蓝色矩形

    return 0;
}

代码分析

抽象原型类(Shape)

  • Shape类 定义了一个接口,包括clone()draw()两个方法。这是原型模式的核心,因为clone()方法提供了一种方式来创建对象的副本。
  • clone()是纯虚函数,强制要求派生类实现具体的克隆逻辑。
  • draw()也是一个虚函数,用于在屏幕上绘制形状。
  • 析构函数是虚的,保证派生类的正确析构。

具体原型类(Circle和Rectangle)

  • Circle类和Rectangle类Shape类的具体实现。它们实现了clone()方法,通过拷贝构造函数来克隆自身,返回一个新的共享指针指向克隆对象。
  • 这些类也实现了draw()方法,具体化了如何绘制自己。
  • Circle类包含颜色和半径属性;Rectangle类包含颜色、宽和高属性。这些属性在构造函数中被初始化。

原型管理器(PrototypeManager)

  • PrototypeManager类 管理一组原型对象,允许客户端注册新的原型或通过名字获取原型的克隆。
  • 使用std::unordered_map来存储字符串键和形状原型的映射。
  • registerPrototype()方法将原型与键绑定存储在映射中。
  • getPrototype()方法通过键来查找并克隆存储的原型。

客户端使用

  • main()函数中,首先创建一个PrototypeManager实例。
  • 注册了两个原型:“large red circle”和“small blue rectangle”。
  • 通过原型名称获取原型的克隆,并调用它们的draw()方法来绘制。
  • 这样客户端可以使用原型模式来避免直接构造具体类的实例,而是通过复制现有实例来创建新实例。

原型模式的编程要点可以总结为以下几个关键方面:

  1. 定义抽象原型类(Prototype):这个类声明了一个克隆自身的接口,通常是一个clone方法,用于生成对象自身的一个完整拷贝。在示例中,Shape是一个抽象原型类,定义了clone()方法作为克隆接口。

  2. 实现具体原型类(Concrete Prototype):具体原型类实现抽象原型的clone方法,在此方法中返回其对象的一个完整拷贝。例如,CircleRectangle类分别重写了clone方法,用来复制自身的实例。

  3. 使用深拷贝:通常在实现clone方法时,需要确保进行深拷贝以避免副本与原始对象之间的依赖。这意味着复制的对象应包括其所有的成员变量的新实例,确保原型的完整和独立。

  4. 原型管理器(Prototype Manager):管理器类用于存储一个原型注册表,其中包含了系统中所有可用的原型对象。管理器提供注册和查找原型的功能。在代码示例中,PrototypeManager类使用一个哈希表存储注册的原型,并通过键值对进行访问和克隆。

  5. 克隆操作的隐含意义:原型模式不仅仅是复制一个对象,更是一种避免复杂对象创建开销的策略,特别是当对象的创建过程复杂或资源消耗较大时。

  6. 客户端的简化操作:客户端可以通过管理器获取已注册的原型的副本,然后根据需要修改这些副本,而无需了解对象的具体类别。这降低了系统的复杂性,并提高了代码的复用性。

优点

  • 降低复杂性:客户端无需知道具体的类,只需要知道如何操作接口。
  • 动态管理:可以在运行时动态地添加或删除原型。
  • 优化性能:通过复制已有对象来避免重新初始化对象,尤其在创建复杂对象时。

潜在的改进点

  • getPrototype()方法中,可能需要增加对键不存在的异常处理。如果键不在映射中,应该返回null或抛出异常。
  • 可以考虑增加一些功能,例如删除注册的原型,或者列出所有注册的原型。

通过实现原型模式,我们可以看到如何有效地复制和管理图形对象。该模式不仅减少了创建复杂对象的复杂性,还提高了系统的灵活性和扩展性。用户可以通过简单地复制和调整现有的图形对象来创建新的对象,这对于图形设计软件是一个巨大的优势。

结语

通过本文的探讨和示例实现,我们详细了解了原型模式在C++中的应用,尤其是在图形编辑软件的开发场景中。原型模式通过允许对象复制自身,为处理如图形对象这样的复杂对象提供了一个高效和灵活的创建策略。

原型模式的核心优势包括:

  • 减少创建操作的复杂性:原型模式使得复制一个复杂对象变得简单,避免了新对象创建过程中需要的重复步骤。
  • 增加程序的动态性:可以在运行时通过注册和删除原型来动态地增加或减少产品的数量。
  • 优化性能和资源利用:通过复制已有对象来避免重复的初始化操作,从而节省了资源和提高了性能,尤其是当对象的创建成本较高时。

在图形编辑器示例中,我们展示了如何利用原型模式来管理和复制图形对象。通过注册不同的图形原型到原型管理器,我们能够在运行时根据需求轻松地复制任何已注册的图形对象。这种方法不仅简化了代码,还增强了系统的可维护性和扩展性。

你可能感兴趣的:(设计模式轻松学,设计模式,原型模式)