如何让单个接口返回不同的数据类型?
Posted
技术标签:
【中文标题】如何让单个接口返回不同的数据类型?【英文标题】:How To Have A Single Interface Return Different Data Types? 【发布时间】:2014-02-14 13:06:03 【问题描述】:简而言之,我想使用单个接口IProducer
创建一个对象IProduct
。 IProduct
将具有不同的组件,具体取决于创建它的接口。然后IProduct
类将被IConsumer
接口使用。应该根据IProduct
的派生类型使用正确的IConsumer
类(我不想自己做类型检查)。
我本质上想使用策略模式(单个接口背后的不同行为),但增加了返回特定于所使用的派生接口的对象的能力。我想遵守 Open/Close 原则,并且在添加更多功能时不会更改任何这些现有类。
我想完成这样的事情(我确定某处的语法是错误的,但请耐心等待):
class IProduct
public:
int intData;
;
class ProductA : public IProduct
public:
float floatData;
;
class ProductB : public IProduct
public:
bool boolData;
;
class IProducer
public:
virtual IProduct* produce(void) = 0;
;
class ProducerA : public IProducer
public:
IProduct* produce(void)
return new ProductA;
;
class ProducerB : public IProducer
public:
IProduct* produce(void)
return new ProductB;
;
class IConsumer
public:
virtual void consume(IProduct* prod) = 0;
;
class ConsumerA : public IConsumer
public:
void consume(IProduct* prod)
//I want to access the float!
;
class ConsumerB : public IConsumer
public:
void consume(IProduct* prod)
//I want to access the bool!
;
void main()
IProducer* producer = ProducerFactory::create("ProducerA");
IProduct* product = producer->produce();
IConsumer* consumer = ConsumerFactory::create("ConsumerA");
consumer->consume(product); //I would like the correct consumer to be used here to deal with the ProductA class
如果您认为有更好的方法来解决这个问题,我会全力以赴。感谢您的帮助!
【问题讨论】:
如果您想将消费者限制在特定种类(或多种)的产品中,听起来您会想要使用dyanmic_cast
。
@typ1232 是的,它必须是引用或指针
我想我已经读过这个,它被称为“切片”。我想我读到如果你使用指针,派生类会保留它的成员,但我不确定。
@trianta2 是的,您必须为此使用指针(抱歉,错过了第一遍)。返回值和函数参数都应该是IProduct*
。
【参考方案1】:
您需要一个将IProduct
实现映射到正确的IConsumer
实现的注册表。基本上它只是地图的抽象:
class ConsumerRegistry
std::map<size_t, std::shared_ptr<IConsumer>> m_consumers;
public:
// we are not responsible for products, so lets allow plain ptrs here for more flexibility and less overhead
std::shared_ptr<IConsumer> GetConsumer(IProduct* product)
auto it = m_consumers.find(typeid(product).hash_code());
if (it == m_consumers.end())
return nullptr;
else
return it->second;
template<typename P>
void RegisterConsumer(std::shared_ptr<IConsumer> consumer)
m_consumers.emplace(typeid(P).hash_code(), consumer);
template<typename P>
void UnregisterConsumer()
m_consumers.erase(typeid(P).hash_code());
;
要么全局公开这个类(例如作为单例),要么在你需要的上下文中使用它。你像这样注册消费者:
reg.RegisterConsumer<ProductA>(new ConsumerA());
reg.RegisterConsumer<ProductB>(new ConsumerB());
我们还可以在 IConsumer
中包含一个 virtual void Register(ConsumerRegistry& reg) = 0;
方法,以便更安全地注册:
void ConsumerA::Register(ConsumerRegistry& reg, std::shared_ptr<IConsumer> self)
IConsumer::Register<ProductA>(reg, self);
// Required for friendship, can be static:
template<typename T>
void IConsumer::Register(ConsumerRegistry& reg, std::shared_ptr<IConsumer> self)
reg->RegisterConsumer<T>(self);
void ConsumberRegistry::RegisterConsumer(std::shared_ptr<IConsumer> consumer)
consumer->Register(*this, consumer);
将Register()
和低级RegisterConsumer()
方法设为私有,并让ConsumerRegistry
和IConsumer
成为朋友。那么可以这样使用:
reg.RegisterConsumer(new ConsumerA());
reg.RegisterConsumer(new ConsumerB());
【讨论】:
【参考方案2】:这是我正在考虑使用的解决方案。如有任何反馈,我将不胜感激。
我将使用访问者模式并像这样引入ProductVisitor
类:
class IProductVisitor
public:
explicit IProductVisitor()
virtual ~IProductVisitor()
virtual void visitA(ProductA* model) = 0;
virtual void visitB(ProductB* model) = 0;
;
class ProductTypeVisitor : public IProductVisitor
public:
typedef enum Unknown, A, B ProductType;
explicit ProductTypeVisitor() : modelType(Unknown)
virtual ~ProductTypeVisitor()
virtual void visitA(ProductA* product)
modelType = A;
virtual void visitB(ProductB* product)
modelType = B;
ProductType getProductType(void)
return modelType;
ProductType modelType;
;
class IProduct
public:
IProduct() : intData(3)
virtual ~IProduct()
int intData;
virtual void accept(IProductVisitor* v) = 0;
;
class ProductA : public IProduct
public:
ProductA() : IProduct(), floatData(5.5)
virtual ~ProductA()
float floatData;
void accept(IProductVisitor* v)
v->visitA(this);
;
class ProductB : public IProduct
public:
ProductB() : IProduct(),boolData(false)
virtual ~ProductB()
bool boolData;
void accept(IProductVisitor* v)
v->visitB(this);
;
在制作我的工厂ConsumerFactor
时,我将使用ProductTypeVisitor
类来确定产品是什么类,动态正确地转换它(基于enum
的状态),然后返回一个消费者用正确的产品初始化。
class ConsumerFactory
public:
explicit ConsumerFactory(void)
IConsumer* createFromProduct(IProduct* product)
ProductTypeVisitor visitor;
product->accept(&visitor);
ProductTypeVisitor::ProductType productType = visitor.getProductType();
IConsumer* consumerPtr;
switch (productType)
case ProductTypeVisitor::A :
consumerPtr = new ConsumerA(dynamic_cast<ProductA*>(product));
break;
case ProductTypeVisitor::B :
consumerPtr = new ConsumerB(dynamic_cast<ProductB*>(product));
break;
default:
std::cout << "Product type undefined. (throw exception)" << std::endl;
break;
return consumerPtr;
private:
ProductTypeVisitor visitor;
;
最后,代码将如下所示:
IProducer* producer = new ProducerA;
IProduct* product = producer->produce();
ConsumerFactory factory;
IConsumer* consumer = factory.createFromProduct(product);
consumer->consume();
唯一指定的地方是ProducerA
。就我而言,这是用户唯一应该指定的内容。此外,我将更改区域隔离为两个类,ConsumerFactory
和 IProductVisitor
(一开始是非常小的更改)。
如果有人可以提供改进或建议,我会全力以赴!
【讨论】:
【参考方案3】:这不是完整的解决方案(也许只是出于好奇),但您始终可以在编译时跟踪类型,并使用桥接模板调用将产品分派给正确的消费者。
#include <iostream>
template <class T>
class IProduct
public:
virtual ~IProduct()
int intData;
typedef T consumer;
;
class ConsumerA;
class ProductA : public IProduct<ConsumerA>
public:
float floatData;
;
class ConsumerB;
class ProductB : public IProduct<ConsumerB>
public:
bool boolData;
;
template <class P, class C>
void apply(P* product, C* consumer)
dynamic_cast<typename P::consumer*>(consumer)->consume(product);
template <class T>
class IConsumer
public:
virtual void consume(IProduct<T>* prod) = 0;
;
class ConsumerA : public IConsumer<ConsumerA>
public:
void consume(IProduct<ConsumerA>* prod)
//I want to access the float!
std::cout << "ConsumerA" << std::endl;
std::cout << dynamic_cast<ProductA*>(prod)->floatData << std::endl;
;
class ConsumerB : public IConsumer<ConsumerB>
public:
void consume(IProduct<ConsumerB>* prod)
//I want to access the bool!
std::cout << "ConsumerB" << std::endl;
std::cout << dynamic_cast<ProductB*>(prod)->boolData << std::endl;
;
int main(int argc, char* argv[])
auto p_a = new ProductA;
auto c_a = new ConsumerA;
apply(p_a, c_a);
auto p_b = new ProductB;
auto c_b = new ConsumerB;
apply(p_b, c_b);
return 0;
【讨论】:
继承在这里丢失了(不知道它是否是一个要求,但我认为是这样),因为IProduct<A>
是一个与IProduct<B>
完全不同的类; c++ 不是 java。
@Paranaix 我不确定我是否理解您的评论。继承保留在需要的地方:IProduct
接口和实际产品之间。否则 dynamic_cast
会失败。但是,如果对从另一个产品继承的产品感兴趣,那么这个解决方案并不是最好的。此外,“c++ 不是 java”是什么意思?微不足道的解释是微不足道的......
在您的示例中,无法保存例如IProduct*
的向量。为什么?因为IProduct<A>
与IProduct<B>
是不同的类。然而,在 Java 中,这样的设计是可能的,因为无论您传递什么类型,IProduct 都保持为 IProduct,因此 c++ is not java
@Paranaix 啊,好点子。如果要坚持这种方法,则可能必须将调度封装在例如函子中并具有向量或那些。但是,正如答案开头所述,这可能更像是一种好奇心。【参考方案4】:
我在尝试解决类似问题时发现了这个问题。我最终使用std::any
(c++17
添加到语言中)来解决我的问题。单个接口可以返回一个std::any
,您可以将其转换回底层类型(通过std::any_cast
)。从广义上讲,std::any
就像在 void*
指针后面“隐藏”类型,但以类型安全的方式完成,具有完整的语言和编译器支持。
见:
std::any: How, when, and why When should I use std::any 相关话题std::variant
。
【讨论】:
以上是关于如何让单个接口返回不同的数据类型?的主要内容,如果未能解决你的问题,请参考以下文章