What should be at the top of inheritance tree of Decorator design pattern?
I mean the base for both components and decorators
- non-abstract class: has data fields, implements methods
- abstract class: has data fields, has only abstract (pure virtual) methods
- interface: no data fields, has only abstract (pure virtual) methods
- abstract class & interface: interface is the base for decorator, abstract class is the base for decorated components
Simple example
IText
– interface declaresgetContent()
methodAbstractText
– abstract class declaresgetContent()
method and definestxt
data
fieldText
– non-abstract class, general, can be also used as base for other classesHelloWorld
– non-abstract class, more specific thanText
HelloSomeone
– non-abstract class, extendsText
with the data fieldname
Decorator
abstract class for decorators (might be redundant?)PrefixDecorator
decorator class prefixesgetContent()
with a string prefixSuffixDecorator
decorator class suffixesgetContent()
with a string suffixgetContent()
method that should return txt with optional additional data (suffixes and prefixes from decorators,name
field in theHelloSomeone
class)
What base class should be chosen?
-
non-abstract class
-
abstract class
-
interface
-
abstract class & interface
Code for last example
#include <iostream>
#include <string>
using namespace std;
/* Base for decorator & abstract: no fields, getContent() is pure virtual */
class IText {
public:
virtual string getContent() = 0;
virtual ~IText() { }
};
/* Base: has a field, getContent() is pure virtual */
class AbstractText : public IText {
protected:
string txt;
public:
virtual ~AbstractText() { cout << " AbstractText dtor" << endl; }
AbstractText(string t = "...") : txt(t) { }
virtual string getContent() = 0;
};
/* Concrete, general */
class Text : public AbstractText {
public:
virtual ~Text() { cout << " Text dtor" << endl; }
Text(string t) : AbstractText(t) { }
virtual string getContent() { return txt; }
};
/* Concrete, more specific */
class HelloWorld : public AbstractText {
public:
virtual ~HelloWorld() { cout << " HelloWorld dtor" << endl; }
HelloWorld() : AbstractText("Hello world") {}
virtual string getContent() { return txt; }
};
/* Additional field */
class HelloSomeone : public AbstractText {
private:
string name;
public:
virtual ~HelloSomeone() { cout << " HelloSomeone dtor" << endl; }
HelloSomeone(string n) : AbstractText("Hello, "), name(n) { }
string getContent() { return (txt + name + "!"); }
};
class Decorator : public IText {
public:
virtual string getContent() = 0;
virtual ~Decorator() { cout << " Decorator dtor" << endl; }
};
class PrefixDecorator : public Decorator {
private:
IText *t;
public:
PrefixDecorator(IText *te) { t = te; }
virtual ~PrefixDecorator() { cout << " PrefixDecorator dtor" << endl; delete t; }
virtual string getContent() { return "---" + t->getContent(); }
};
class SuffixDecorator : public Decorator {
private:
IText *t;
public:
SuffixDecorator(IText *te) { t = te; }
virtual ~SuffixDecorator() { cout << " SuffixDecorator dtor" << endl; delete t; }
virtual string getContent() { return t->getContent() + "---"; }
};
int main() {
IText *t = new Text("some text");
IText *hw = new HelloWorld();
IText *hs = new HelloSomeone("Mark");
cout << t->getContent() << endl;
cout << hw->getContent() << endl;
cout << hs->getContent() << endl;
t = new PrefixDecorator(t);
hw = new PrefixDecorator(hw);
hw = new SuffixDecorator(hw);
cout << t->getContent() << endl;
cout << hw->getContent() << endl;
cout << "DELETE Text" << endl;
delete t;
cout << "DELETE Helloworld" << endl;
delete hw;
cout << "DELETE HelloSomeone" << endl;
delete hs;
}
2
What should be at the top of inheritance tree of Decorator design pattern?
How to discriminate:
- non-abstract class – Only if it makes sense in your code to instantiate it in client code (also see: liskov substitution)
- abstract class or interface – most common case; This is when it doesn’t make sense for client code to instantiate it; To distinguish between abstract class and interface: If you look through your code and find that all specializations have data in common, move it to the base class (otherwise, remain with the interface).
- abstract class and interface – if there are two cases when a part of your specializations have common code and a part don’t, extract common code to separate class, and you end up with both cases.
My criteria is usually not derived from some pure rules that I respect to implement the decorator; Instead, I try to optimize for maintenance instead of purity.
You can also see that I do not particularly differentiate between abstract classes and interfaces; this is because in C++ there are no interfaces – only abstract classes (so the distinction feels a bit artificial).
It depends entirely on what you want. These variations exist for your convenience, not because one way is a better way than another.
Personally I’m partial to the non-abstract class, because it requires the fewest number of classes, which in my opinion is always a plus. However, you may find yourself in the situation in which there is an AbstractText
class or IText
and an Text
implementation, in which case you may model your Decorator pattern accordingly.
If you do decide to choose the non-abstract class variation, it is probably the easiest to adapt into the other variations later since it is usually easier to add classes than to remove them, so I would give the non-abstract class variation a try.
1