Это вопрос о конкретной реализации ОО в C ++ данной проблемы:
Существует несколько вариантов алгоритма, скажем, два, каждый из которых имеет несколько общих частей. Эти алгоритмы (их реализации, AlgImpl) получают параметры запуска. Они также получают входные данные, которые они обрабатывают, по частям. Существуют различные способы предоставления параметров запуска (из файла, из сети, из пользовательского ввода), а также различные способы получения данных (из файла, с устройства). Существует общий API для разных источников данных, аналогично опциям.
Архитектура должна позволять использовать любой доступный AlgImpl вместе с любыми доступными источниками опций и данных. Он также должен позволять добавлять новые AlgImpl и, возможно, новые типы источников для опций и данных с минимальным или нулевым изменением исходного кода (скажем, только с двумя AlgImpl, двумя типами источников опций и двумя типами источников данных).
Вот что я думаю об этом в терминах C ++, используя наследование, агрегацию и указатели. Поскольку все AlgImpl имеют некоторые общие части, естественно организовать их вокруг базового абстрактного класса, поэтому (исключая несущественные типы данных) мы имеем:
class BaseAlgorithm
{
... //Abstract class with common code
};
class SimpleAlgorithmImpl: public BaseAlgorithm
{
...
};
class OptimalAlgorithmImpl: public BaseAlgorithm
{
...
};
Теперь параметры и источники данных имеют одинаковый интерфейс соответственно:
class BaseOptionsSource
{
public:
//Interface
virtual void GetOptions(Options& opts) = 0;
};
class FileOptionsSource: public BaseOptionsSource
{
void GetOptions(Options& opts);
...
};
class NetworkOptionsSource: public BaseOptionsSource
{
void GetOptions(Options& opts);
...
};
class BaseDataSource
{
public:
//Interface
virtual void GetDataChunk(DataChunk& chunk) = 0;
};
class FileDataSource: public BaseDataSource
{
void GetDataChunk(DataChunk& chunk);
...
};
class DeviceDataSource: public BaseDataSource
{
void GetDataChunk(DataChunk& chunk);
...
};
Опции и источники данных затем становятся членами BaseAlgorithm:
class BaseAlgorithm
{
public:
BaseAlgorithm(BaseDataSource* pDataSrc, BaseOptionsSource* pOptsSrc);
BaseDataSource* _pDataSrc;
BaseOptionsSource* _pOptsSrc;
};
BaseAlgorithm::BaseAlgorithm(BaseDataSource* pDataSrc, BaseOptionsSource* pOptsSrc):
_pDataSrc(pDataSrc), _pOptsSrc(pOptsSrc)
{
}
Затем объект парулярного алгоритма может быть создан следующим образом:
DeviceDataSource dataSrc;
NetworkOptionsSource optsSrc;
SimpleAlgorithmImpl simpleAlg(&dataSrc, &optsSrc);
Для нового источника AlgImpl или нового типа его класс должен реализовывать унаследованные методы. Конечно, перед созданием объекта AlgImpl должен быть код «if / else if / …», который явно выбирает набор используемых AlgImpl и исходные тексты.
Исходные объекты также могут быть повторно использованы в этом случае при условии, что объект AlgImpl не управляет распределением / освобождением исходных объектов, переданных ему.
Как вы думаете, это правильный способ сделать это в C ++? Или, может быть, существует какой-то другой, более простой, более гибкий или менее «беспроблемный» шаблон для реализации такого взаимозаменяемого подфункции? Неужели в этом случае неизбежно использование указателей?
Выглядит разумно для меня. Я думаю, что указатели хороши, если вы можете быть уверены, что источник данных и источник опций работают в течение всего времени существования алгоритма. Я не думаю, что указатели неизбежны, хотя. Вот несколько других опций (некоторые из которых C ++ 11):
Рекомендации
Желательно постоянные ссылки. Возможно, немного безопаснее, чем указатели, если вы передаете их в конструктор, и вам не нужно, чтобы они обнулялись.
BaseAlgorithm(BaseDataSource& dataSrc,
BaseOptionsSource& optsSrc) :
dataSrc_(dataSrc),
optsSrc_(optsSrc) {}
Общие умные указатели
Если вы не можете быть уверены, что источник данных или источник опций живы в течение всего времени существования алгоритма, рассмотрите возможность передачи std::shared_ptr
или же std::weak_ptr
, использование std::shared_ptr
если ты хочешь оставить их в живых, std::weak_ptr
если вы хотите знать, живы ли они.
BaseAlgorithm(std::weak_ptr<BaseDataSource> dataSrc,
std::weak_ptr<BaseOptionsSource> optsSrc) :
dataSrc_(dataSrc),
optsSrc(optsSrc) {}
тонуть
Это зависит от того, как эти источники данных, источники опций и алгоритмы создаются и используются, но, возможно, это упростит владение, если алгоритм получит право собственности на источник данных и / или источник опций и будет проходить мимо unique_ptr.
BaseAlgorithm(std::unique_ptr<BaseDataSource> dataSrc,
std::unique_ptr<BaseOptionsSource> optsSrc) :
dataSrc_(std::move(dataSrc)),
optsSrc(std::move(optsSrc)){}
Несколько незначительных моментов: я спрашиваю, является ли выходной параметр лучшим способом получения опций и блоков данных. Не бойтесь возврата по значению даже для чего-то большого, например, для блока данных. Копия всегда будет оптимизирована любым современным компилятором, и это облегчает чтение и запись вызывающего кода:
Options opts = optsSrc_->GetOptions(opts);
DataChunk data = dataSrc_->GetDataChunk(data);
Возможно, вы упустили их, чтобы упростить свой пост, но мне интересно, если GetOptions
и / или GetDataChunk
может быть const
? Должны ли они вызывать изменения в источнике? Если они могут быть const
это означает, что алгоритмам нужен только константный указатель / ссылка на источник, и это может немного облегчить поиск ошибок. Вы говорите о повторном использовании исходных объектов, это может быть нецелесообразно, если алгоритмы могут вносить изменения в исходные объекты. Например, что произойдет, если вы будете вызывать GetDataChunk несколько раз, вы каждый раз получаете разные порции? Это может иметь неожиданные последствия, если вы можете использовать один и тот же источник данных в разных алгоритмах.
Наконец, убедитесь, что _pOptsSrc и _pDataSrc protected
или же private
не публично
Других решений пока нет …