4
\$\begingroup\$

This is a C++ implementation of the decorator pattern. I am also learning about interfaces and (pure) virtual functions.

#include <iostream>
#include <string>

// interface

class AbstractObject {
protected:
    std::string name;
    virtual void displayTo(std::ostream&) const = 0;
public:
    friend class Decorator;
    friend std::ostream& operator<<(std::ostream&, const AbstractObject&);

    AbstractObject(std::string name) : name(name) {}
};

std::ostream& operator<<(std::ostream& os, const AbstractObject& object) {
    object.displayTo(os);
    return os;
}

// implementation

class Object : public AbstractObject {
protected:
    void displayTo(std::ostream& os) const {
        os << name;
    }
public:
    Object(std::string name): AbstractObject(name) {}
};


class Decorator : public AbstractObject {
protected:
    const AbstractObject& ref_decoratedObject;
    void displayTo(std::ostream& os) const {
        os << name << " decorates ";
        ref_decoratedObject.displayTo(os);
    }
public:
    Decorator(std::string name, const AbstractObject& ref_decoratedObject)
    : AbstractObject(name), ref_decoratedObject(ref_decoratedObject) {}
};


int main() {
    AbstractObject& human = Object("Alice");
    AbstractObject& shirt = Decorator("blue shirt", human);
    AbstractObject& jacket = Decorator("red jacket", shirt);

    std::cout << human << std::endl;
    std::cout << jacket << std::endl;

    getchar();

    return 0;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Decoraters are a nice pattern. But in C++ it is more common to use templates and Duck Typing to achieve the same affect. \$\endgroup\$ – Martin York Dec 17 '17 at 19:04
2
\$\begingroup\$

Friend Functions.

Its a lot simpler to just place friend functions directly into the class declaration.

Rather than:

class AbstractObject {
    friend std::ostream& operator<<(std::ostream&, const AbstractObject&);
};

std::ostream& operator<<(std::ostream& os, const AbstractObject& object) {
    object.displayTo(os);
    return os;
}

Simply do this:

class AbstractObject {
    friend std::ostream& operator<<(std::ostream& os, const AbstractObject& object) {
        object.displayTo(os);
        return os;
    }
};

Avoid protected

Protected very rarely gives you any benefit. It should be avoided.

class AbstractObject {
protected:
    // All the people that use `name` are already friends.
    // So protected is not useful. Other people using name
    // should be calling `displayTo()` to get the name displayed. 
    // But the real question is why does an abstract object have
    // a name? If i does you should know how to display it.
    std::string name;

    // Personally I see no reason not to make this public.
    // But you could make it private and only callable by your
    // friend the output operator.
    virtual void displayTo(std::ostream&) const = 0;
};

Classes with Virtual methods should have virtual destructor

When you create a class with a virtual method. This means you are calling that method through an object of another type. This type is usually dynamically allocated and thus will be dynamically destroyed.

If you delete it via a pointer to the base class the wrong destructor will be called (unless it it virtual).

AbstractObject*  obj = new Decorator("red jacket", shirt);

// Do stuff.

delete obj; // Here obj is an AbstractObject* so we call AbstractObject::~AbstractObject()
            // to destroy the object. This is wrong as obj points at a
            // Decorator object and we want to call Decorator::~Decorator()
            // To achieve this the AbstractObject destructor must be
            // virtual to make sure that chaining to the most
            // derived version of the function happens.

Temporary abuse:

    AbstractObject& human = Object("Alice");
    AbstractObject& shirt = Decorator("blue shirt", human);
    AbstractObject& jacket = Decorator("red jacket", shirt);

Sure that works. But I can not call any Object methods on human as all I can see is the AbstractObject interface. Same with shirt and jacked I can not see their properties or fields.

It would be better to do it like this:

    Object    human("Alice");
    Decorator shirt("blue shirt", human); // this works as Object is an AbstractObject 
    Decorator jacket("red jacket", shirt);// This works as Decorator is an AbstractObject

Design

Yes decorator can be a useful pattern.
But in C++ we usually use "Duck Typing" to solve the same problem.

Duck Typing: If it quacks like a duck it must be duck like.
             If it has the same method name  with the same parameters
             then we should be able to use it in the same way.

This is usually used in conjunction with templates.

template<typename Duck>
void print(Duck const& duck, std::ostream& str) {
    duck.displayTo(str);
}

Here we have a print() method. It takes any type (don't have to be related) as long as they support the displayTo(std::ostream& str) method the above function will work.

Refactored original

#include <iostream>
#include <string>

class AbstractObject
{
    public:
        virtual ~AbstractObject() {}
        friend std::ostream& operator<<(std::ostream& os, const AbstractObject& object)
        {
            object.displayTo(os);
            return os;
        }
        virtual void displayTo(std::ostream&) const = 0;
};

class Object : public AbstractObject
{
    private:
        std::string name;
    public:
        Object(std::string name)
            : name(name)
        {}
        void displayTo(std::ostream& os) const
        {
            os << name;
        }
};

class Decorator : public Object
{
    private:
        const AbstractObject& ref_decoratedObject;
    public:
        Decorator(std::string name, AbstractObject const& ref_decoratedObject)
            : Object(name)
            , ref_decoratedObject(ref_decoratedObject)
        {}
        void displayTo(std::ostream& os) const
        {
            Object::displayTo(os);
            os << " decorates " << ref_decoratedObject;
        }
};



int main() {
    Object      human("Alice");
    Decorator   shirt("blue shirt", human);
    Decorator   jacket("red jacket", shirt);

    std::cout << human << std::endl;
    std::cout << jacket << std::endl;
\$\endgroup\$
  • \$\begingroup\$ If you’ll place it inside, the function will be invokable only by ADL. It might surprise IDE, and some temporary might not work. \$\endgroup\$ – Incomputable Dec 17 '17 at 19:36

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.