3

I follow this example on decorator patterns to implement objects who's functionality can be dynamically be coupled together. Namely:

class I: is the interface class, common to both "core" class and "decorator base" class
class A: is the "core functionality class"
class D: is the "decorator base" class
classes X,Y,Z: inherit from the "decorator base" class and extend functionality of the "core class" dynamically.

Normally instantiating an object that has all the functionality would look like:

I * anYZ = new Z( new Y( new A ));
I * anXY = new Y( new X( new A ));
I * anXYZ = new Z( new Y( new X( new A )));

OR

Z * anYZ = new Z( new Y( new A ));
Y * anXY = new Y( new X( new A ));
Z * anXYZ = new Z( new Y( new X( new A )));

The latter group are especially useful if I want to use a function such as Z::doSmth() , which is not defined in class "I".

This has worked great so far, because all of the common functionality has been placed in class "A" and most the non common functionality has been categorized in classes XYZ. Additionally (and this is why I used decorator pattern) at instantiation I will select the pairs or triplets of functionality I want.

Focusing on the derived decorator classes "XYZ", say I have a pure virtual method in each of them:

struct X /*and class Y  and class Z*/ : public D {
    virtual void doSmthElse() = 0;
};

A different doSmthElse() method should be executed for different instantiations of "anXY","anXYZ", etc.

What is the correct way to do this?

Would a derived class K : public X , public Y , public Z work? It feels that this method "destroys" the whole concept of the decorator pattern. It also feels like "diamond" problem would surface this way, and while "virtual inheritance" would solve it, I would still need to clarify to the compiler which of the same-named functions of classes "XYZ" should be called when accessed from an object of type "K". An instantiation would then look like

K * anK = new K( new A );

Alternatively, I could derive each of "XYZ" like so:

struct KX : public X {
  void doSmthElse(){} //one implementation of doSmthElse
};

struct LX : public X {
  void doSmthElse(){} //another implementation of doSmthElse
};

struct KY : public Y {
  void doSmthElse(){} //yet another implementation of doSmthElse
};

Then I can again select functionality at instantiation time like so

Z * anKxYZ = new Z( new Y( new KX( new A )));
Z * anLxYZ = new Z( new Y( new LX( new A )));
Z * anXKyZ = new Z( new KY( new X( new A )));

This is simpler and avoids headaches of the previous method, however, again it feels like I will be recreating several new classes.

I haven't been able to google about it as I do not know if this "problem" has a name and perhaps a coding pattern that solves it. If this is a well known problem and you can point me in a direction to read something, or you can think of a different solution I would gladly hear about it.

2 Answers 2

2

I have a strong feeling your "decorator pattern" doesn't match conventional usage of that term.

Commonly, the usage of this pattern implies extra functionality in the methods provided by the interface, e.g. logging ("method X was called with params Y and Z") or bounds checking (method on A expects param X to be between values Y and Z, decorator verifies this).

With that in mind, one doesn't need a decorator base class - a decorator class simply implements the interface (also, decorators usually don't publish own methods, since they should nearly always be seen as an instance of the implemented interface, in your case, of type I* or I&).

If i understand your problem correctly, you want to introduce new functionality using new methods (on your wrappers, since as far as I can tell, they aren't decorators), but don't know exactly how to make all those added methods visible in the resulting object. In that case, i would suggest using a different approach: composition. (You might also want to take a look at the strategy pattern.)

Example:

class A : public I {
    X* myX;
    Y* myY;
    Z* myZ;
public:
    A() { /* ... */ }
    void doSomething() { /* ... */ }

    X* getX() { return myX; }
    void setX(X* newX) {
        if(myX != nullptr) delete myX;
        myX = newX;
    }
    // repeat for Y and Z
};

// similar for Y and Z
class X {
public:
    // i left out constructor and stuff
    void doSomethingX() {
        // implement X specific methods
    }
};

Usage:

void useSomethingofX(A* aObject) {
    if(aObject->getX() != nullptr) {
        // use X functionality
        aObject->getX()->doSomethingX();
    } else {
        // fallback or throw or ignore (X functionality not present
    }
}

void useSomethingOfXandYandZ(A* aObject) {
    if(aObject->getx() == nullptr || aObject->getY() == nullptr
        || aObject->getZ() == nullptr) return;

    aObject->getX()->doSomethingX();
    aObject->getY()->doSomethingY();
    aObject->getZ()->doSomethingZ();
}
2

As far as I can tell, this is probably not a well known problem. I have played a lot with classes and interfaces and decorators, and yet I never encountered such a problem. What this probably means is that I avoided putting myself in the position of having such a problem, by employing other well known best practices of refactoring and simlification on my designs, so this problem never had the chance to arise.

It seems to me that if you have to do so exotic things with your decorators, then perhaps your original interface "I" is too large, too rich. There is no shame in extracting part of the functionality of "I" into a separate interface (say, "J") and a method like J* getJ() in "I". This way, different concerns are divided in different interfaces, each interface has its own decorators, and then delegation starts becoming a viable proposition: when getJ() is invoked, you can of course return this, but you can also return myJmember which implements J on its own, without anyone having to derive from it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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