samedi 23 septembre 2023

How to correctly apply the Decorator pattern (C++)

I am currently working on a simple WYSIWYG project to exercise my skills with design patterns. Basically, the program lets the user create, move and resize basic shapes, like rectangles or triangles. Now, I am trying to implement a mouse selection feature using the Decorator pattern, but I am facing a hard time with it. My main problem is that the decorator object must have acess to member variables in the derived shapes to draw the selection on screen. If I follow the canonical examples in the textbooks, I would have to inject those variables in the decorator by reference, but that would not be sustainable, the more member variables I introduce in the derived shapes later on.

class Shape {
public:
virtual void Draw(Window* window) = 0;
};

class Decorator : public Shape {
private:
Shape* shape;
public:
Decorator(Shape* shape) : shape(shape) {}
void Draw(Window* window){shape->Draw(window);}
};

class SelectionDecorator : public Decorator {
private:
RECT& rect; //SelectionDecorator needs a reference to RECT to draw the selection border
public:
SelectionDecorator(Shape* shape, RECT& rect) : Decorator(shape), rect(rect){}
void Draw(Window* window) {
//draw selection
}
};

class RectangleShape : public Shape {
private:
RECT rect;
public:
void Draw(Window* window) {
//draw rectangle
}
};

Duck typing would avoid the variable duplication. My biggest concern with this strategy is the fact that, if I wish to use the Visitor pattern to send messages to the decorator, I would have to treat it as a generic Shape or I would have to declare a different Visit algorith in the Visitor interface for each type of Shape I use, which contradicts the whole pattern purpose.

class Shape{
public:
virtual void Draw(Window* window)=0;
};

template <typename T> class SelectionDecorator : public Shape {
private:
T shape;
public:
Decorator(T shape) : shape(shape) {}
void Draw(Window* window){shape->DrawDecoratedShape(window);}
};

class RectangleShape : public Shape {
private:
RECT rect;
public:
void Draw(Window* window) {
//draw rectangle without selection
}
void DrawDecoratedShape(Window* window) {
//draw rectangle with selection
};
};

Another BIG question I have is where I should store the pointers to the decorators I create. If, for example, I declare a class Document, that contains a list of Shapes I want to display on screen, everytime I select a shape with a mouse click, I have to create a decorator object and replace the pointer to the basic shape in Document by the decorator pointer. I highly doubt that's how the selection feature concretely works in real life programs, since it represents, in my opinion, a contradiction to the Open Clode principle.

I am starting to think that real programs do not use the Decorator pattern for selection purposes. Instead, they may add a boolean member variable to each object to store the selection state (which is a very ugly solution) or use the Strategy pattern as a replacement to the Decorator. Is that really so?

Aucun commentaire:

Enregistrer un commentaire