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

时间:2014-01-23 19:18:12

标签: c++ design-patterns polymorphism strategy-pattern

简而言之,我想使用单个界面IProducer来创建一个对象IProductIProduct将拥有不同的组件,具体取决于创建它的接口。然后IProduct接口将使用IConsumer类。应根据IConsumer的派生类型使用正确的IProduct类(我不想自己进行类型检查)。

我本质上想要使用策略模式(单个接口背后的不同行为),但增加了返回特定于所使用的派生接口的对象的能力。我希望遵守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
}

如果你认为有更好的方法可以解决这个问题,那我就听见了。谢谢你的帮助!

4 个答案:

答案 0 :(得分:0)

您需要的是一个注册表,它将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());

我们还可以在virtual void Register(ConsumerRegistry& reg) = 0;内部使用IConsumer方法,以便更安全地注册:

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());

答案 1 :(得分:0)

这是我正在考虑使用的解决方案。我很感激任何反馈。

我将使用访问者模式并像这样引入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(这些都是非常小的更改)。

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

答案 2 :(得分:0)

这不是完整的解决方案(也许只是好奇心),但你总是可以在编译时跟踪类型,并使用桥接模板化调用将产品发送给正确的消费者。

#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;
}

答案 3 :(得分:0)

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

请参阅: