将继承重构为组合,保持C ++中的多态功能

时间:2014-12-24 08:04:24

标签: c++ inheritance interface object-composition

我可能会遇到一个问题,今天我想做好充分的准备。该问题涉及C ++上下文中的继承,多态和组合。我们如何重构"继承代码重用"成分,仍然能够保持多态方法?。

我在这里寻找的是更多的"动手"关于此事的指导。我来了一个非常简单的例子来向您展示,我相信您将能够阅读它并将其改进为我需要的答案。

class Multilingual_entity {
public:    
    enum class t_languages {LAN_ENGLISH, LAN_RUSSIAN, LAN_CHINESE};

private:    
    std::map<t_languages, std::string> texts;

public:
    std::string set_text(t_language t, const std::string s) {texts[t]=s;}
    void get_text(t_language t) const {return texts.at(t);}
}

后来这样延长......

class Category_shopping_article:public Multilingual_entity {
private:
    unsigned int pk_identifier;

public:
    unsigned int get_pk() const {return pk_identifier;}
    //....
}

class Shopping_article:public Multilingual_entity {
private:   
    unsigned int category_identifier;
    float price;

public:
    //....
}

并像这样应用:

void fetch_all_titles_for(Multilingual_entity& m);

Category_shopping_article get_category(unsigned int pk) {
    Category_shopping_article result=get_me_category_by_pk(pk);
    fetch_all_titles_for(result);
    return result;
}

std::vector<Shopping_article> get_articles_by_category(const Category_shopping_article& cat) {
    std::vector<Shopping_article> result=get_me_articles_by_category_id(cat.get_pk());
    for(Shopping_article& a : result) fetch_all_titles_for(a);
    return result;
}

正如你所看到的,一切都非常简单:我可以用这个定义一个小的购物目录(第一个想到的例子)并将它以存储在某处的各种语言呈现给用户。假设语言存储在数据库中,以便&#34; fetch_all_titles_for&#34;看起来像这样:

void fetch_all_titles_for(Multilingual_entity& m) {
    Database_table T=m.get_database_language_table();   //I know, this is not implemented.
    Database_criteria C=m.get_database_language_criterie(); //Nor is this.

    std::map<Multilingual_entity::t_languages, const::std::string> texts=Database_object::get_me_the_info_i_need(T, C);
    for(const std::pair<Multilingual_entity::t_languages, const::std::string>& p : texts) m.set_texts(p.first, p.second);
}

嗯,让我们说这是一个非常有限的快速启动,因为明天我会想要添加另一个多语言文本属性&#34;到文章所以我可以有一个描述。我不需要在类别中进行描述,因此我无法将其放在Multilingual_entity基类中......也许后天我会添加一个&#34; text_review&#34;一切都会更加破碎,所以我们进入了组合车:

class Category_shopping_article: {
private:
    unsigned int pk_identifier;
    Multilingual_entity titles;

public:
    unsigned int get_pk() const {return pk_identifier;}

    std::string set_title(t_language t, const std::string s) {titles.set_text(t, s);}
    void get_title(t_language t) const {return titles.get_text(t);}
}


class Shopping_article: {
private:    
    unsigned int category_identifier;
    float price;

    Multilingual_entity titles;
    Multilingual_entity descriptions;

public:     
    std::string set_title(t_language t, const std::string s) {titles.set_text(t, s);}
    void get_title(t_language t) const {return titles.get_text(t);}

    std::string set_description(t_language t, const std::string s) {descriptions.set_text(t, s);}
    void get_description(t_language t) const {return descriptions.get_text(t);}
}

好的,很好......现在有这些转发方法(我猜是可以容忍的)但是我完全打破了&#34; fetch_all_titles_for(Multilingual_entity&amp; m)&#34;因为没有Multilingual_entity了。我很熟悉&#34;更喜欢构成而非继承&#34;经验法则,但在示例的开头,有一个基类可以提供有关查找语言数据的信息。

这里的问题是......我是否必须利用权衡取舍或者我在这里遗漏了什么?是否有类似界面的解决方案可以帮助我解决这个问题?我想到了类似的东西:

class Multilingual_consumer {
private:
    std::vector<Multilingual_entity> entities;

public:     
    Multilingual_entity& add_entity() {
        entities.push_back(Multilingual_entity);
        return entities.back();
    }
    Multilingual_entity& get_entity(unsigned int i) {return entities.at(i);}
};

class Category_shopping_article:public Multilingual_consumer {
private:
    unsigned int pk_identifier;
    enum entities{TITLE, DESCRIPTION};

public:
    unsigned int get_pk() const {return pk_identifier;}

    Category_shopping_article() {
        add_entity();
        add_entity();   //Ugly... I know to come with something better than this but I could store references to these entities.
    }

    void get_title(Multilingual_entity::t_language t) const {return get_entity(TITLE).get_text(t);}
    void get_description(Multilingual_entity::t_language t) const {return get_entity(DESCRIPCION).get_text(t);}
}

但似乎有很多障碍。关于如何能够组成许多多语言属性的对象并让它们具有可扩展性的任何想法?。

感谢。

1 个答案:

答案 0 :(得分:1)

一个简单的解决方案是将MultilingualEntity实例作为该类的公共成员:

class ShoppingItem {
    public:
        MultilingualEntity title;
        MultilingualEntity description;
        MultilingualEntity tech_specs;
    private:
        ...
};

通过这种方式,您可以直接访问这些方法,而无需创建额外的名称和写入转发器。

如果你是一个常见的偏执狂,你也可以用

来更难改变它们
class ShoppingArticle {
    public:
        const MultilingualEntity& title() const { return title_; }
        const MultilingualEntity& description() const { return description_; }
        const MultilingualEntity& tech_specs() const { return tech_specs_; }
    private:
        MultilingualEntity title_;
        MultilingualEntity description_;
        MultilingualEntity tech_specs_;
        ...
};

只需要为合成的每个元素添加一行。

要编写处理具有多语言实体部分的对象的泛型函数,您可以使用基于方法指针的访问器:

// Search elements matching in current language
template<typename T>
std::set<int> searchItems(const std::string& needle,
                          const std::vector<T>& haystack,
                          const MultilingualEntity& (T::*a)() const) {
    std::set<int> result;
    for (int i=0,n=haystack.size(); i<n; i++) {
        if (match(needle, (haystack[i].*a)().get(current_language))) {
            result.insert(i);
        }
    }
    return result;
}

然后使用它传递访问器:

std::set<int> result = searchItems("this", items, &ShoppingItem::title);