在不改变接口的情况下为对象添加功能

时间:2013-03-12 12:35:37

标签: c++ design-patterns c++11

我有一个对象作为接口的引用/指针。如果存在该方法,我想在具体对象上调用一个方法,而不需要更改接口,破坏封装或编写任何可怕的黑客攻击。怎么办呢?

这是一个例子。

我有一个界面:

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

这个界面的多个具体实现:

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_;
};

抽象SuperChatty::AddToDictionary接口中不存在IChatty方法,尽管它可以包含在另一个新接口中。

在现实世界中,这些对象是通过工厂构建的,它们本身是抽象接口的具体实例。然而,就我们的目的而言,这与手头的问题是正交的:

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

由于AddToDictionary不是IChatty界面的一部分(不能成为其中的一部分),我可以称之为。

如何在AddToDictionary指针上调用chatty而不破坏封装,编写一些可怕的黑客,或采用任何其他设计快捷方式?

注意:在现实世界中,字典是SuperChatty对象本身的一部分,不能与它分开。

注意2:我不想向下转换为具体类型。

4 个答案:

答案 0 :(得分:6)

将词典作为一个对象,可由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();
    }
};

用法:

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

修改

好的,问题改变了。

如果你正确地做到这一点,你需要将自己与糟糕的基础系统隔离开来:

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;
};

这些实现为:

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;
};

答案 1 :(得分:5)

使它从另一个接口派生,只需检查是否可以将对象转换为该接口。

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;
}

答案 2 :(得分:0)

主要问题是您正在丢弃所需的信息。

因此,主要的解决方案是不丢弃信息,但没有足够的代码来充实细节。

其次,使用dynamic_cast来解决一个简单的kludge解决方案:

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;
}

你可以安全地转发,因为知道静态类型IChatty是多态的。

答案 3 :(得分:0)

对于这个特定的例子,没有理由不像这样创建对象:

SuperChatty* chatty = new SuperChatty;

chatty->AddToDictionary("foo");

您仍然可以将上述细分中的chatty作为IChatty指针或参考传递,例如

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

[同样用于将chatty存储在vector<IChatty*>或类似的内容中。

我的观点是,如果您要使用“新”接口函数,那么您可能还想创建具有新接口的类。

添加代码以“尝试投射”等等,很快就会变得非常混乱,并且容易出错。