1
\$\begingroup\$

I have just started reading the GO4 book to learn the OOD concepts. In order to practice the Prototype pattern, I implemented a small example (the idea for colored shapes was taken from "refactoring.guru"). Following is my code with some questions beneath.

Prototype definition:

enum class Shape
{
    Circle,
    Rectangle,
};

class ColoredShapePrototype
{
protected:
    std::string color_;
public:
    ColoredShapePrototype() {}
    ColoredShapePrototype(std::string color) : color_(color) {}
    virtual ~ColoredShapePrototype() {}
    virtual ColoredShapePrototype* Clone() const = 0;
    virtual void ShapeDetails() { std::cout << "Color: " << color_ << "\n"; } 
    virtual void UpdateColor(int color) { color_ = color; }
};

class ColoredCirclePrototype : public ColoredShapePrototype
{
private:
    int radius_;
public:
    ColoredCirclePrototype(std::string color, int raduis) : ColoredShapePrototype(color), radius_(raduis) {}
    ColoredShapePrototype* Clone() const override { return new ColoredCirclePrototype(*this); }
    void ShapeDetails() { ColoredShapePrototype::ShapeDetails(); std::cout << "Radius: " << radius_ << "\n"; }
};

class ColoredRectanglePrototype : public ColoredShapePrototype
{
private:
    int height_;
    int width_;
public:
    ColoredRectanglePrototype(std::string color, int height, int width) : ColoredShapePrototype(color), height_(height), width_(width) {}
    ColoredShapePrototype* Clone() const override { return new ColoredRectanglePrototype(*this); }
    void ShapeDetails() { ColoredShapePrototype::ShapeDetails(); std::cout << "Height: " << height_ << "\nWidth:" << width_ << "\n"; }
};

class ShapesPrototypeFactory
{
private:
    std::unordered_map<Shape, ColoredShapePrototype*> prototypes_;
public:
    ShapesPrototypeFactory() {
        prototypes_[Shape::Circle] = new ColoredCirclePrototype("White", 5);
        prototypes_[Shape::Rectangle] = new ColoredRectanglePrototype("White", 2, 3);
    }
    ~ShapesPrototypeFactory() {
        delete prototypes_[Shape::Circle];
        delete prototypes_[Shape::Rectangle];
    }
    ColoredShapePrototype* CreatePrototype(Shape shape) { return prototypes_[shape]->Clone(); }
};

Usage in main:

ShapesPrototypeFactory prototype_factory;
ColoredShapePrototype* circ = prototype_factory.CreatePrototype(Shape::Circle);
circ->ShapeDetails();
ColoredShapePrototype* rect = prototype_factory.CreatePrototype(Shape::Rectangle);
rect->ShapeDetails();
delete circ;
delete rect;

Questions:

  1. Interfaces in general - pure virtual functions and other variables are declared, and derived classes should implement those. Suppose I want to add a specific capability (with unique data member and member function) to the derived - Does it contradict the interface idea? (Because, if I have a pointer from base to derived, this pointer will not recognize the specific method...). If it is OK, Is there a way to execute a derived function through the interface(who doesnt have this function..)?

  2. while debugging and watching circ variable, I can see the variable in the memory window, and I see "white" color, but I cant find the radius there. Is there a way i can see specific data member address in the memory? (I have tried circ->radius_ etc. none was working..)

  3. I have seen that most implementations are using "unique_ptr". I know its basic usage, but how critical it is? and why not use it all the times rather than a regular pointer actually? and where are the places I should use it in the prototype pattern?

  4. Implementing Copy Constructor in case of non-stack variables (shallow/deep copying) for the clone operation - Is a CCtor in the base class is sufficient or the derived is required as well?

  5. Code review - better syntax/design/ideas/improvements?

Thanks in advance, I will highly appreciate your help!

\$\endgroup\$
2
  • \$\begingroup\$ What's the GO4 book and who's "refactoring.guru"? What problem does this code solve? What prompted you to write this? \$\endgroup\$
    – Mast
    Aug 28 '20 at 19:15
  • \$\begingroup\$ this is a design patterns book, and that website is a random website i encountered while looking for an example. I want to make sure I used the pattern correctly and if there are better ways to write it \$\endgroup\$
    – Mor
    Aug 29 '20 at 7:00
2
\$\begingroup\$

Let's go through your questions.

  1. No, it does not contradict the idea.

    The base class should define the common functionality of the derived classes, that's needed by some system to operate on such objects. However, part of the code of that system, or a different part of the code base may be dedicated to working only with some of the derived classes, in which case the functions and variables should not be defined in the base class.

    Using your example, a rendering system may require a Render() method and variables defining the visual characteristics of a shape, which should be part of the colored shape interface. On the other hand, a system that works with shapes that have corners will not be expected to work on circles. In which case you would dynamic_cast a shape to the derived type in that system and call the methods specific to the derived class.

  2. Depending on the debugger you're using, you are probably using a watch window to view the values of the circ variable. The watch window will display circ as a variable of type ColoredShapePrototype*, which only has a color variable.

    In Visual studio, for example, watch window will allow you to cast the variable to a ColoredCirclePrototype*, by typing (ColoredCirclePrototype*)circ. You will then see the radius member of circ.

  3. Smart pointers avoid common problems with managing dynamically allocated memory. unique_ptr specifically avoids leaks by deallocating the memory at the end of the scope, unless it is moved to another unique_ptr. If the clone() function returns a unique_ptr, this means the user of your prototype pattern code won't need to bother remembering to dealocate the memory. However, you are enforcing him to use a specific implementation of a smart pointer, in this case the standard library's unique_ptr. If this prototype pattern is part of a library and used in a project that has a different implementation of unique_ptr, it can become more complicated to manage this difference than if you return a plain raw pointer.

  4. If a derived class has members that need to be deep copied, then that class needs to define a copy constructor that performs the deep copy.

  5. My feedback

  • The most significant issue I see is that someone else can further derive a class from your derived classes, and there's nothing to remind them that the clone method needs to be overriden. Let's consider creating a ColoredSquarePrototype that inherits ColoredRectanglePrototype. If we create a square object and try to clone it, we would call the ColoredRectanglePrototype::clone() which copy constructs a rectangle, not a square. This is not easy to solve, one option is to declare your derived classes final, so they cannot be further derived. If you wish to allow derivations, you could read further about the issue in this blog post: https://katyscode.wordpress.com/2013/08/22/c-polymorphic-cloning-and-the-crtp-curiously-recurring-template-pattern/
  • I don't see the benefit of providing a default constructor in ColoredShapePrototype, it just allows the usage of shapes with an unitialized color ("").
  • You should pass a const string& to the constructors to avoid copying.
  • Removing "Prototype" from the name of the shape classes will convey better that these classes represent actual shapes instead of some auxiliary implementation.
  • The virtual void UpdateColor(int color) incorrectly takes an integer parameter instead of a string. This compiles, because the integer is converted to a char for std::string::operator=(const char) to be invoked.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.