如何让单个接口返回不同的数据类型?

Posted

技术标签:

【中文标题】如何让单个接口返回不同的数据类型?【英文标题】:How To Have A Single Interface Return Different Data Types? 【发布时间】:2014-02-14 13:06:03 【问题描述】:

简而言之,我想使用单个接口IProducer 创建一个对象IProductIProduct 将具有不同的组件,具体取决于创建它的接口。然后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&amp; 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() 方法设为私有,并让ConsumerRegistryIConsumer 成为朋友。那么可以这样使用:

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。就我而言,这是用户唯一应该指定的内容。此外,我将更改区域隔离为两个类,ConsumerFactoryIProductVisitor(一开始是非常小的更改)。

如果有人可以提供改进或建议,我会全力以赴!

【讨论】:

【参考方案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&lt;A&gt; 是一个与IProduct&lt;B&gt; 完全不同的类; c++ 不是 java。 @Paranaix 我不确定我是否理解您的评论。继承保留在需要的地方:IProduct 接口和实际产品之间。否则 dynamic_cast 会失败。但是,如果对从另一个产品继承的产品感兴趣,那么这个解决方案并不是最好的。此外,“c++ 不是 java”是什么意思?微不足道的解释是微不足道的...... 在您的示例中,无法保存例如IProduct* 的向量。为什么?因为IProduct&lt;A&gt;IProduct&lt;B&gt; 是不同的类。然而,在 Java 中,这样的设计是可能的,因为无论您传递什么类型,IProduct 都保持为 IProduct,因此 c++ is not java @Paranaix 啊,好点子。如果要坚持这种方法,则可能必须将调度封装在例如函子中并具有向量或那些。但是,正如答案开头所述,这可能更像是一种好奇心。【参考方案4】:

我在尝试解决类似问题时发现了这个问题。我最终使用std::anyc++17 添加到语言中)来解决我的问题。单个接口可以返回一个std::any,您可以将其转换回底层类型(通过std::any_cast)。从广义上讲,std::any 就像在 void* 指针后面“隐藏”类型,但以类型安全的方式完成,具有完整的语言和编译器支持。

见:

std::any: How, when, and why When should I use std::any 相关话题std::variant

【讨论】:

以上是关于如何让单个接口返回不同的数据类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何在单个查询中计算不同类型列的流数据帧的统计信息?

SQL 数据库存储不同类型的值(在单个字段中或模拟为单个字段)

C ++如何从具有不同返回类型的接口多重继承?

使用单个接口验证不同函数的参数类型(不使用检查)

如何存储多个数据类型的数组

pandas tolist() 如何返回不同的数据类型?