Adding functionality to an object without changing the interface

I have an object represented as a link / pointer to an interface. I would like to call a method on a specific object if this method is present, without changing the interface, breaking encapsulation, or writing any horrible hacks. How can I do that?

Here is an example.

I have an interface:

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

And a few specific implementations 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 not present in the IChatty abstract interface, although it can be included in another new interface.

In the real world, these objects are built at the factory, concrete concrete instances of the abstract interface themselves. However, for our purposes, which are orthogonal to the problem under consideration:

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

Since AddToDictionary not part of the IChatty interface (and cannot be part of it), I can name it.

How can I call AddToDictionary in the chatty pointer without breaking encapsulation, writing some terrible hack, or accepting any other development shortcuts?

NOTE. In the real world, a dictionary is part of the SuperChatty object SuperChatty and cannot be separated from it.

NOTE2: I do not want to drag a specific type.

+6
source share
4 answers

The dictionary has an object that you can update and specify 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(); } }; 

Using:

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

change

Well, the question has changed.

If you do it right, you need to isolate yourself from a bad base 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; }; 

They 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; }; 
+6
source

Draw a conclusion from another interface and just check if you can pass an object to this interface or not.

 class IDictionary { public: virtual ~IDictionary() {}; virtual void AddToDictionary(const std::string& word) = 0; }; class SuperChatty : public IChatty, public IDictionary { ... as before ... }; int main() { IChatty* chatty = new SuperChatty; IDictionary *dict = dynamic_cast<IDictionary*>(chatty); if (dict) dict->AddToDictionary("foo"); std::cout << chatty->Speak() << std::endl; } 
+5
source

The main problem is that you are discarding the necessary information.

So, the main decision is not to throw out the information, but not enough code to reflect the details of this.

Secondly, the tekcical kludge solution is to simply downcast using dynamic_cast :

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

You can safely omit it because the known static type IChatty is polymorphic.

0
source

In this particular example, there is no reason not to create an object as follows:

 SuperChatty* chatty = new SuperChatty; chatty->AddToDictionary("foo"); 

You can pass chatty in the specified segment as a pointer or IChatty link, for example.

 void Talk(IChatty *ch) { ch->Speak(); } 

[Similarly for storing chatty in vector<IChatty*> or something like that].

My point is that if you are going to use the "new" interface features, then you probably also want to create a class with a new interface.

Adding code to "attempts to quit", etc. very quickly becomes very dirty and error prone.

0
source

All Articles