Преобразование наследования в композицию с сохранением полиморфных возможностей в Stack Overflow

Я мог бы столкнуться с проблемой в будущем, и я хотел бы быть хорошо подготовленным к этому сегодня. Проблема касается наследования, полиморфизма и композиции в контексте 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);}
}

Хорошо, хорошо … Теперь есть эти методы пересылки (допустимо, допустимо), но я полностью нарушил любой подход к «fetch_all_titles_for (Multilingual_entity»)& m) «так как Multilingual_entity больше не существует. Я знаком с эмпирическим правилом« предпочитаю композицию, а не наследование », но в начале примера имел смысл иметь базовый класс, который мог бы предоставить информацию о том, где искать язык данные.

Вот вопрос … Должен ли я использовать компромиссы или я что-то здесь упускаю? Есть ли подобное интерфейсу решение, которое поможет мне в этом? Я думал о чем-то вроде:

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

Решение

Простое решение будет оставить ваш 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);
1

Другие решения


По вопросам рекламы [email protected]