Я надеюсь, что название не вводит в заблуждение, но я не смог найти правильный язык, чтобы определить мою проблему в одном вопросе.
Недавно я пытался реализовать шаблон проектирования «CQRS Command Handler» с использованием C ++. У меня есть 2 иерархии, которые я должен жениться вместе:
ICommand
struct ICommand
{
virtual ~ICommand() = default;
};
struct SampleCommand : ICommand
{
int sampleParameter;
SampleCommand() : sampleParameter(0)
{
}
explicit SampleCommand(const int sampleParameter)
{
this->sampleParameter = sampleParameter;
}
};
ICommandHandler
template<typename T, typename = std::enable_if_t<std::is_base_of<ICommand, std::decay_t<T>>::value>>
struct ICommandHandler
{
virtual void Handle(std::shared_ptr<T> command) = 0;
virtual ~ICommandHandler() = default;
};
class SampleCommandHandler : public ICommandHandler<SampleCommand>
{
public:
void Handle(std::shared_ptr<SampleCommand> command) override
{
std::cout << "sampleParameter " << command->sampleParameter << std::endl;
}
};
Последний фрагмент, который мне нужно реализовать, — это диспетчер, который принимает команду, находит обработчик и делегирует команду найденному обработчику. Первая идея, которая пришла мне в голову, состояла в том, чтобы раскрыть некоторый API регистрации обработчиков в диспетчере и написать метод диспетчеризации, который просто попытался бы выполнить динамическую трансляцию всех зарегистрированных обработчиков, и если какое-то приведение было успешным, он вызвал бы найденный обработчик, как показано ниже:
class Dispatcher
{
public:
template<typename T>
void Dispatch(std::shared_ptr<T> command)
{
auto handler = std::find_if(std::begin(_handlers), std::end(_handlers), [](auto handler)
{
return dynamic_cast<ICommandHandler<T>*>(handler);
});
if(handler != std::end(_handlers))
{
(*handler)->Handle(command);
}
}
private:
std::vector<?> _handlers;
};
Вопрос в том, какого типа должно быть хранилище «_handlers» std :: vector, чтобы метод Dispatcher :: Dispatch работал нормально и возможно ли это?
Что я уже пробовал:
станд :: вектор< ICommandHandler *> — Не скомпилирован, потому что приведение конкретного обработчика к ICommandHandler< ICommand> *> не возможно.
Error C2440 'initializing': cannot convert from 'SampleCommandHandler *' to 'ICommandHandler<ICommand,void> *'
станд :: вектор< void *> — не скомпилирован, так как dynamic_cast нельзя применить к void *
У вас тут куча бессмысленного (по крайней мере для меня) полиморфизма здесь; Я бы разделил контейнер обработчиков на основе того, что они обрабатывают, вместо того, чтобы использовать один вектор. Как карта / неупорядоченная карта от typeindex до обработчика; или иметь фиксированный набор типов для обработки.
Но для решения проблемы попросили:
struct ICommandHandlerBase{
virtual ~ICommandHandlerBase(){};
};
template<typename T, typename = std::enable_if_t<std::is_base_of<ICommand, std::decay_t<T>>::value>>
struct ICommandHandler:ICommandHandlerBase
{
Теперь сохраните вектор ICommandHandlerBase*
или же unique_ptr<ICommandHandlerBase>
,
Конечно, используйте карту или неупорядоченную карту для поиска. Если необходимо, вы можете использовать (неупорядоченный) набор с пользовательским компаратором. Хотя это не отвечает на ваш вопрос 🙂
Предполагая, что ваши обработчики действительно полиморфны, тогда ваша карта или набор должны быть указателями на интерфейс ICommandHandler. Они не должны быть необработанными указателями, но могут быть разновидностью умного указателя.
Использование std :: shared_ptr означает, что время обработки вашего объекта обработчика гарантируется при обработке события — если оно также принимает shared_ptr, даже если событие удаляется из отображения (либо внутри обработчика, либо в другом потоке или обработчике), но поставляется с добавлена внутренняя сложность.
Реализация std :: unique_ptr намного проще и может хорошо работать, когда вы знаете, что обработчик не может быть прерван. Обратите внимание, что клиентский код видит только необработанный указатель — он не должен красть unique_ptr!