Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding Functionality To An Object Without Changing The Interface

I have an object presented as a reference/pointer to an interface. I would like to call a method on the concrete object if that method is present, without changing the interface, breaking encapsulation, or writing any horrible hacks. How can it be done?

Here's an example.

I have an interface:

class IChatty
{
public:
    virtual ~IChatty() {};
    virtual std::string Speak() const = 0;
};

And multiple concrete implementation of this interface:

class SimpleChatty : public IChatty
{
public:
    ~SimpleChatty() {};

    virtual std::string Speak() const override
    {
        return "hello";
    }
};

class SuperChatty : public IChatty
{
public:
    void AddToDictionary(const std::string& word)
    {
        words_.insert(word);
    }
    virtual std::string Speak() const override
    {
        std::string ret;
        for(auto w = words_.begin(); w != words_.end(); ++w )
        {
            ret += *w;
            ret += " ";
        }
        return ret;
    }
private:
    std::set<std::string> words_;
};

The SuperChatty::AddToDictionary method is not present in the abstract IChatty interface, although it could be included in another, new interface.

In the real world, these objects are constructed through factories, themselves concrete instantiations of an abstract interface. However for our purposes that's orthogonal to the problem at hand:

int main()
{
    IChatty* chatty = new SuperChatty;
    chatty->AddToDictionary("foo");
    std::cout << chatty->Speak() << std::endl;
}

Since AddToDictionary isn't part of the IChatty interface (and can't be part of it), I can's call it.

How can I call AddToDictionary on the chatty pointer without breaking encapsulation, writing some horrible hack, or taking any other design shortcuts?

NOTE: In the real world, the dictionary is part of the SuperChatty object itself, and cannot be separate from it.

NOTE2: I do not want to downcast to the concrete type.

like image 399
John Dibling Avatar asked Mar 12 '13 12:03

John Dibling


People also ask

Which pattern is used to implement functionality on an already created object?

Decorator Pattern The decorator design pattern is used to modify the functionality of an object at runtime. At the same time, other instances of the same class will not be affected by this, so individual object gets the modified behavior.

What happens if we create object of interface?

Generally, it contains abstract methods (except default and static methods introduced in Java8), which are incomplete. Still if you try to instantiate an interface, a compile time error will be generated saying “MyInterface is abstract; cannot be instantiated”.

Can we able to create object for interface?

Like abstract classes, interfaces cannot be used to create objects (in the example above, it is not possible to create an "Animal" object in the MyMainClass) Interface methods do not have a body - the body is provided by the "implement" class.

Why object is not create for interface?

Since we cannot call the methods in the interface, there is no need for creating the object for interface and there is no need of having a constructor in it.


1 Answers

Have dictionary be an object which can be updated and referenced by SuperChatty:

class Dictionary {
public:
    void add(const std::string& word);
    const std::set<std::string>>& words() const;
    //..
};

class SuperChatty : public IChatty
{
public:
    SuperChatty(Dictionary& dictionary) :
    dictionary(dictionary) {
    }

    virtual std::string Speak() const override
    {
        auto words = dictionary.words();
        ostringstream oss;
        copy(words.begin(), words.end(),
             ostream_iterator<string>(oss, " "));
        return oss.str();
    }
};

Usage:

int main()
{   
    Dictionary dictionary;
    IChatty* chatty = new SuperChatty(dictionary);
    dictionary.add("foo");
    std::cout << chatty->Speak() << std::endl;
}

edit

Okay, the question changed.

If you're doing this properly, you need to isolate yourself from the bad underlying system:

struct Dictionary {
    virtual ~Dictionary () {}
    virtual void add(const std::string& word) = 0;
};

struct Instrumenter {
    virtual ~Instrumenter () {}
    virtual void addDictionary(Dictionary& dictionary) = 0;
};

struct Chatter {
    virtual ~Chatter() {}
    virtual string speak() const = 0;
    virtual void instrument(Instrumenter& instrumenter) = 0;
};

These are implemented as:

class BasicChatter : public Chatter {
    virtual string speak() const {
        return chatty.Speak();
    }
    virtual void instrument(Instrumenter& instrumenter) {
        // do nothing
    }
private:
    SimpleChatty chatty;
};

class SuperChatter : public Chatter {
    SuperChatter () : dictionary(chatty);

    virtual void instrument(Instrumenter& instrumenter) {
        instrumenter.addDictionary(dictionary);
    }

    virtual string speak() const {
        return chatty.Speak();
    }
private:
    SuperChatty chatty;
    DictionaryImpl dictionary;
};
like image 169
Peter Wood Avatar answered Sep 24 '22 06:09

Peter Wood