C++ 协变参数 - 设计模式
C++ Covariant Parameters - Design Pattern
以下面简化的C++ class层级为例。我想要完成的是 Service
提供了一个用于保存任意 Model
对象的虚拟方法。但是 Service
的每个子 class,例如BoxService
应该也只能保存 Box
个对象。
由于 C++ 不支持方法参数的协变,我不能简单地在 BoxService.h
中声明保存方法,例如:
void save(Box box);
我的问题是,是否有针对该问题的首选设计模式或最佳实践?或者,如果到达的模型对象是 Box 类型,我是否应该检查 BoxService.cpp
中保存函数的实现,否则抛出异常?
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);
};
盒子Service.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();
}
};
进一步的步骤:
- 设计一个给出编译时错误而不是断言的解决方案。
- 允许在不更改(大量)现有代码的情况下添加更多模型类型和更多操作。这应该等同于表达式问题。请注意,这种按模型类型对操作进行分组的方法需要进行更广泛的更改以添加新操作,而不是添加新模型类型。对于使用访问者和按操作对模型类型进行分组的情况,情况正好相反。表达式问题有解决方案,例如 object algebras,但它们可能更晦涩。
这也许是一个更实用的方法:
将每个模型类型与其实现配对:
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);
}
};
以下面简化的C++ class层级为例。我想要完成的是 Service
提供了一个用于保存任意 Model
对象的虚拟方法。但是 Service
的每个子 class,例如BoxService
应该也只能保存 Box
个对象。
由于 C++ 不支持方法参数的协变,我不能简单地在 BoxService.h
中声明保存方法,例如:
void save(Box box);
我的问题是,是否有针对该问题的首选设计模式或最佳实践?或者,如果到达的模型对象是 Box 类型,我是否应该检查 BoxService.cpp
中保存函数的实现,否则抛出异常?
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);
};
盒子Service.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();
}
};
进一步的步骤:
- 设计一个给出编译时错误而不是断言的解决方案。
- 允许在不更改(大量)现有代码的情况下添加更多模型类型和更多操作。这应该等同于表达式问题。请注意,这种按模型类型对操作进行分组的方法需要进行更广泛的更改以添加新操作,而不是添加新模型类型。对于使用访问者和按操作对模型类型进行分组的情况,情况正好相反。表达式问题有解决方案,例如 object algebras,但它们可能更晦涩。
这也许是一个更实用的方法:
将每个模型类型与其实现配对:
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);
}
};