полиморфизм — C ++ ковариантные параметры — шаблон проектирования

Возьмем следующую упрощенную иерархию классов 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'
}

2

Решение

Таким образом, вы говорите, что хотите сгруппировать реализации операций по типу модели. Я объясню более ООП подход.

отдельный 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();
}
};

Дальнейшие шаги:

  • Инженер решение, которое дает ошибку времени компиляции вместо утверждения.
  • Разрешить добавление как большего количества типов моделей, так и дополнительных операций без существенного изменения существующего кода. Это должно быть эквивалентно проблеме выражения. Обратите внимание, что этот подход группировки операций по типу модели требует гораздо более широкого изменения для добавления новой операции, чем для добавления нового типа модели. Обратное будет верно для использования модели посетителей и группирования типов по операциям. Существуют решения проблемы выражения, такие как объектные алгебры, но они могут быть немного более неясными.
1

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

Вот, пожалуй, более функциональный подход:

Соедините каждый тип модели с его реализацией:

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);
}
};
1