4

将来、問題が発生する可能性がありますが、今日はそれに備えたいと思います。この問題は、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;
}

ご覧のとおり、すべて非常に簡単です。これを使用して小さなショッピング カタログ (頭に浮かんだ最初の例) を定義し、さまざまな言語でユーザーに提示してどこかに保存することができます。言語がデータベースに保存されているとします。そのため、「fetch_all_titles_for」は次のようになります。

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

さて、これは非常に限定的なジャンプスタートであるとしましょう。なぜなら、明日、別の「多言語テキスト プロパティ」を記事に追加して説明を付けたいからです。カテゴリの説明は必要ないので、Multilingual_entity 基本クラスに入れることはできません... たぶん、明後日には「text_review」を追加し、すべてがさらに壊れるので、構成に入りますワゴン:

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

わかりました...これらの転送方法があります(許容できると思います)が、Multilingual_entityがもうないため、「fetch_all_titles_for(Multilingual_entity&m)」へのアプローチを完全に破りました。私は「継承よりも構成を優先する」という経験則に精通していますが、例の冒頭で、言語データを調べる場所に関する情報を提供できる基本クラスを持つことは理にかなっています。

ここに質問があります...トレードオフを活用する必要がありますか、それとも何かが欠けていますか?. これに役立つインターフェイスのようなソリューションはありますか? 私は次のようなことを考えました:

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

しかし、ハードルが高いようです。多くの多言語プロパティのオブジェクトを構成し、それらを拡張可能にする方法についてのアイデアはありますか?.

ありがとう。

4

1 に答える 1

1

MultilingualEntity簡単な解決策は、インスタンスをクラスのパブリック メンバーとして残すことです。

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

このようにして、追加の名前を作成したりフォワーダーを作成したりすることなく、メソッドに直接アクセスできます。

あなたがconstパラノイアなら、おそらくそれらを変異させにくくすることもできます

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

コンポジションの要素ごとに 1 行追加するだけです。

多言語エンティティ パーツを持つオブジェクトを処理する汎用関数を作成するには、たとえば、メソッド ポインター ベースのアクセサーを使用できます。

// 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);
于 2014-12-24T08:27:35.510 に答える