Возьмем следующую упрощенную иерархию классов C ++ в качестве примера. Что я хочу сделать, это то, что Service
предоставляет виртуальный метод для сохранения произвольного Model
объекты. Но каждый подкласс Service
например, BoxService
должен и может только сохранить Box
объекты.
Из-за того, что C ++ не поддерживает ковариацию в параметрах метода, я не могу просто объявить метод save в BoxService.h
лайк:
void save(Box box);
Мой вопрос: есть ли предпочтительный шаблон дизайна или лучшие практики для этой проблемы? Или я должен проверить в реализации функции сохранения в BoxService.cpp
если прибывающий объект Model имеет тип Box и в противном случае выдает исключение?
model.h
class Model {
private:
int id;
};
Box.h
class Box : public Model {
private:
int size;
};
Service.h
class Service {
public:
virtual void save(Model model);
};
BoxService.h
class BoxService : public Service {
public:
void save(Model box);
};
BoxService.cpp
void BoxService::save(Model box) {
// TODO: save box and make sure that box is of type 'Box' and not any other subtype of 'Model'
}
Таким образом, вы говорите, что хотите сгруппировать реализации операций по типу модели. Я объясню более ООП подход.
отдельный Service
из реализаций, но мы собираемся избавиться от надоедливого параметра:
class Service { ... };
class ServiceImpl {
virtual ~ServiceImpl() {}
void save() const = 0;
void remove() const = 0;
};
Каждая реализация будет облегченной и будет поддерживать операции, но примет параметр в конструкторе:
class BoxService : public ServiceImpl {
Box _b;
public:
BoxService(Box b) : _b(b) {}
void save() const { ... }
void remove() const { ... }
};
Теперь у нас есть абстрактная фабрика для создания реализаций, когда они нам нужны:
class ServiceImplFactory {
public:
std::unique_ptr<ServiceImpl> create(Model m) const {
if (auto b = dynamic_cast<Box*>(m)) {
return std::make_unique<BoxService>(*b);
} else if (auto x = dynamic_cast<X*>(m)) {
return std::make_unique<XService>(*x);
} else {
assert(!"Not all Models covered by Service implementations");
}
}
};
Сейчас Service
:
class Service {
ServiceImplFactory _implFactory;
public:
void save(Model m) const {
return _implFactory.create(m)->save();
}
void remove(Model m) const {
return _implFactory.create(m)->remove();
}
};
Дальнейшие шаги:
Вот, пожалуй, более функциональный подход:
Соедините каждый тип модели с его реализацией:
template<typename T, typename ExtraType>
struct WithType {
T value;
using extra_type = ExtraType;
WithType(T value) : value(value) {}
};
Определить Model
как вариант вместо иерархии наследования:
using Model = std::variant<WithType<Box, BoxService>, WithType<X, XService>>;
Теперь посетите вариант:
class Service {
public:
void save(Model m) const {
visit([](auto withService) {
typename decltype(withService)::extra_type service;
service.save(withService.value);
}, m);
}
void remove(Model m) const {
visit([](auto withService) {
typename decltype(withService)::extra_type service;
service.remove(withService.value);
}, m);
}
};