Вы на самом деле не описали проблему дизайна. Вы описали некоторые варианты реализации, которые вы использовали, и препятствия, с которыми вы столкнулись, но мы не знаем причин выбора.
Вы сообщаете нам, что Algo
получает право собственности на бизнес через указатель на полиморфный интерфейс AbstractBusiness
и должен предоставить геттер для данных этого бизнеса, хотя ему не известен конкретный тип этих данных (поскольку он не знает конкретный тип бизнеса).
Ни на один из этих вопросов нет очевидных ответов:
- Почему
Algo
должен приобретать бизнес через полиморфный интерфейс?
- Почему
Algo
должен предоставлять геттер для данных своего бизнеса?
Но решение, что это должно быть так, приводит к блокпосту.
Полиморфная выбоина и как из нее выбраться
Q1. заставляет нас задаться вопросом, какова мотивация AbstractBusiness
? Банально, можно с уверенностью сказать, что вы хотите, чтобы он предоставлял единый интерфейс для манипулирования и запросов всех видов бизнеса конкретных типов, которые могут быть определены во время выполнения.
Чтобы полностью соответствовать этой цели, AbstractBusiness
будет инкапсулировать необходимый и достаточный интерфейс для выполнения всех операций и запросов по конкретным предприятиям, которые, как разумно ожидать, могут понадобиться приложениям (включая, помимо прочего, ваши собственные). Назовите этот план А. Вы обнаружили, что он не полностью подходит для плана А. Если приложению необходимо время от времени манипулировать или запрашивать «данные» бизнеса, которые представлены ему через AbstractBusiness
, тогда интерфейс AbstractBusiness
должен предоставлять полиморфные методы для выполнения всех этих манипуляций и запросов, и каждый конкретный бизнес-класс должен реализовать их соответствующим образом для типа содержащихся в нем данных.
Где у вашего AbstractBusiness
проблема:
?unknownType? result();
вам нужно закодировать виртуальные методы, отвечающие на все убедительные ответы на вопрос: Что приложение может захотеть узнать о условном result()
или сделать с ним?
В этом свете выдвинутое предложение ввести еще один полиморфный интерфейс, AbstractData
, предок всех конкретных data
типов всех конкретных предприятий, можно рассматривать как предложение компенсировать необходимые методы, отсутствующие в AbstractBusiness
, путем отдельно инкапсулируя их в спасательную абстракцию. Лучше доделать недоделку AbstractBusiness
.
Возможно, это все хорошо и основано на Писании, но, возможно, то, что на самом деле помешало вам закончить AbstractBusiness
, — это восприятие того, что данные BusinessX
могут существенно отличаться от данных BusinessY
, так что невозможно придумать единый набор полиморфных методов, необходимый и достаточный для управления обоими.
Если это так, то это означает, что нельзя всем бизнесом управлять через единый абстрактный интерфейс. AbstractBusiness
не может быть полностью приспособлен для этой цели, и, если у него есть роль, его роль может заключаться только в управлении полиморфными объектами, представляющими более специализированные абстракции, BusinessTypeX
, BusinessTypeY
и т. д., внутри каждого из которых разнообразие, если таковое имеется, конкретных типов могут работать с одним полиморфным интерфейсом.
AbstractBusiness
будет представлять только тот интерфейс, который используется всеми предприятиями. У него вообще не будет result()
, и вызывающий объект, получивший указатель на AbstractBusiness
с намерением сделать что-то с вещью, возвращенной BusinessTypeX::result()
, продолжит динамическое приведение исходного указателя к BusinessTypeX *
и вызов result()
через целевой указатель только в том случае, если его ненулевой.
Мы до сих пор не знаем, какова мотивация AbstractBusiness
. Мы только что выдвинули довольно правдоподобную мысль о том, что у вас есть «хрестоматийные» амбиции по этому поводу — план А — и либо не поняли, что вы просто не закончили его, либо поняли, что разнообразие данных, которые вы работа с не позволяет вам завершить его в соответствии с планом А и не иметь плана Б. План Б заключается в следующем: углубить полиморфную иерархию и использовать dynamic_cast<LowerType *>(HigherType *)
для обеспечения безопасного доступа к LowerType
интерфейсу, когда он опережает HigherType
. [1]
На очереди Q2. в настоящее время. Скорее всего, причина для Algo::result()
проста: потому что класс готов предоставить геттеры, которые напрямую отвечают на естественные запросы клиента, и в этом случае естественный запрос предназначен для данных, принадлежащих бизнесу, которому принадлежит по Algo
. Но если Algo
знает свой бизнес только как AbstractBusiness
, то он просто не может вернуть данные, принадлежащие его бизнесу, потому что уже увиденные причины означают, что AbstractBusiness
не может вернуть "данные" в Algo
или что-то еще.
Algo::result()
неправильно понимается так же, как AbstractBusiness::result()
неправильно понимается. Учитывая, что данные BusinessX
s и BusinessY
s, возможно, потребуется запрашивать либо с помощью некоторого репертуара виртуальных методов, которые все еще TODO
в AbstractBusiness
(план A), либо, возможно, с помощью методов BusinessX
и BusinessY
, которые вообще не унаследованы от AbstractBusiness
(план B). , единственный запрос, который Algo
, безусловно, может и должен поддерживать в отношении своего бизнеса, состоит в том, чтобы вернуть указатель AbstractBusiness
, через который он владеет своим бизнесом, предоставляя вызывающей стороне возможность запросить через указатель или понизить его, если они могут, до более низкого уровня. интерфейс, который они хотят запросить. Даже если можно закончить AbstractBusiness
по плану А, идея о том, что отсутствующий отчет о методах должен быть продублирован в интерфейсе Algo
только для того, чтобы вызывающему объекту никогда не приходилось получать и понижать указатель AbstractBusiness
, неубедительна. Будет ли этому соответствовать каждый тип, который управляет указателем AbstractBusiness
?
Подводя итог, если у AbstractBusiness
есть веская причина для существования, то вам нужно либо закончить его в соответствии с Планом А и проработать последствия этого, либо сократить его до попытки стать достаточным интерфейсом для управления всеми предприятиями и подкрепите его расширенной полиморфной иерархией, которую клиенты согласовывают с помощью динамического приведения согласно плану Б; и в любом случае вы должны довольствоваться Algo
и подобными заданиями в торговле AbstractBusiness
только для того, чтобы вернуть их указатель AbstractBusiness
клиентам, которые специально его используют.
Лучше не ходите туда
Но вопрос о том, есть ли у AbstractBusiness
веская причина для существования, все еще нерешен, и если вы обнаружите, что вынуждены прибегнуть к плану Б, это само по себе сделает вопрос более острым: когда выяснится, что абстрактный интерфейс, брошенный как корневой класс единой иерархии наследования, не может предоставить план А, тогда возникает сомнение в мудрости архитектуры, которую он изображает. Динамическое приведение для обнаружения и получения интерфейсов является неуклюжим и дорогим способом управления потоком и особенно досадным, когда - как вы нам говорите, это ваша ситуация - область, которая должна будет выполнять канцелярскую канитель, уже знает тип, который она должна "выйти" тип, который он "вставил". Должны ли все типы, которые являются несовершенными потомками корневой абстракции, иметь одного предка по причине, иной, чем единообразие интерфейса (поскольку это не дает им этого)? Экономия универсальных интерфейсов — постоянная цель, но является ли полиморфизм времени выполнения правильным средством или хотя бы одним из правильных средств для их реализации в контексте вашего проекта?
В вашем наброске кода AbstractBusiness
служит не какой-либо конечной цели, а предоставляет тип, который может единообразно заполнять определенные слоты в классе Algo
, в результате чего Algo
может правильно работать с любым типом, который демонстрирует определенные черты и поведение. Как показано, единственное требование Algo
s к уточняющему типу состоит в том, что он должен иметь метод result()
, который что-то возвращает: ему все равно что. Но тот факт, что вы выражаете требования Algo
s к уточняющему типу, указывая, что он должен быть AbstractBusiness
, запрещает не заботиться о том, что возвращает result()
: AbstractBusiness
не может выполнять этот result()
метод, хотя любой из его потомков может сделать.
Предположим в этом случае, что вы увольняете AbstractBusiness
с работы по обеспечению общих атрибутов типов, с которыми может работать Algo
, и вместо этого позволяете Algo
делать это самому, сделав его шаблоном? - поскольку похоже, что то, что AbstractBusiness
делает для Algo
, служит цели параметра шаблона, но саботирует эту самую цель:
#include <memory>
template<class T>
class Algo {
std::unique_ptr<T> concreteBusiness_;
public:
explicit Algo(T * concreteBusiness)
: concreteBusiness_{concreteBusiness}{};
auto result() { return concreteBusiness_->result(); }
};
#include <valarray>
#include <algorithm>
struct MathBusiness {
std::valarray<float> data_{1.1,2.2,3.3};
float result() const {
return std::accumulate(std::begin(data_),std::end(data_),0.0);
}
};
#include <string>
struct StringBusiness {
std::string data_{"Hello World"};
std::string result() const { return data_; }
};
#include <iostream>
int main()
{
Algo<MathBusiness> am{new MathBusiness};
auto ram = am.result();
Algo<StringBusiness> as{new StringBusiness};
auto ras = as.result();
std::cout << ram << '\n' << ras << '\n';
return 0;
}
Вы видите, что при таком способе переноса универсальности с AbstractBusiness
на Algo
первый остается полностью излишним и, таким образом, удаляется. Это краткая иллюстрация того, как введение шаблонов полностью изменило подход к дизайну C++, сделав полиморфные проекты устаревшими для большинства их предыдущих приложений по сравнению с созданием универсальных интерфейсов.
Мы работаем, исходя из наброска контекста вашей проблемы: возможно, еще не видно веских причин для существования AbstractBusiness
. Но даже если они есть, они сами по себе не являются причинами того, что Algo
не является шаблоном или имеет какую-либо зависимость от AbstractBusiness
. И, возможно, они могут быть устранены одно за другим с помощью подобных процедур.
Превращение Algo
в шаблон по-прежнему может быть для вас нежизнеспособным решением, но если это не так, то проблема существенно шире, чем мы видели. И в любом случае уберите это эмпирическое правило: шаблоны для универсальных интерфейсов; полиморфизм для адаптации поведения интерфейса во время выполнения.
[1] Другой план может выглядеть так: инкапсулировать «данные» каждой конкретной компании в boost::any
или std::experimental::any
. Но вы, вероятно, сразу видите, что это по сути то же самое, что и идея инкапсулировать данные в абстракцию спасения, используя готовую абстракцию швейцарской армии, а не создавать свою собственную. В любом виде эта идея по-прежнему оставляет звонящим возможность понизить абстракцию до типа реального интереса, чтобы выяснить, есть ли у них это, и в этом смысле это вариант плана Б.
person
Mike Kinghan
schedule
11.10.2015
concreteBusiness
доAlgo.return()
. Это то, что вы предлагаете, или я неправильно понял? - person Timtro   schedule 10.10.2015Algo
может вернуть, это только некотороеAbstractData
, которое требует другого приведения. Это не сильно отличается отvoid*
. Учитывая, чтоAlgo
, очевидно, не знает, что данные, возвращаемыеBusiness
, означают, поэтому он их не использует. Нужно ли ему принимать участие в возврате результата? Вызывающий точно знает тип данных, используемых в конкретном потомкеAbstractBusiness
, так почему бы просто не спросить этого потомка? - person Kuba Wyrostek   schedule 10.10.2015concreteBusiness
. Я пытался сохранить это в тайне, но, возможно, мне это не нужно, и я просто слишком стараюсь ради метода «добытчика». Я также должен сказать, чтоa.concreteBusiness->data_
немного уродлив, но не нарушает договоренности. - person Timtro   schedule 10.10.2015a.concreteBusiness->data_
не будет работать, так как вы все еще не знаете типconcreteBusiness
здесь. Только самый внешний вызывающий объект знает о типе. - person Kuba Wyrostek   schedule 10.10.2015auto b = std::unique_ptr<AbstractBusiness>{std::move(new Business())};
вы теряете информацию о фактическом типе вовлеченногоAbstractBusiness
. Но если вы сохраните ссылку наnew Business()
, то компилятор прекрасно понимает, что_data
в этой ссылке имеет типstd::valarray<float>
. - person Kuba Wyrostek   schedule 10.10.2015std::function
, он позволяет вам извлекать базовые данные только в том случае, если вы знаете тип. - person melak47   schedule 10.10.2015