【问题标题】: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*

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


【解决方案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* 指针后面“隐藏”类型,但以类型安全的方式完成,具有完整的语言和编译器支持。

      见:

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-22
        • 1970-01-01
        • 2019-05-04
        • 1970-01-01
        • 2021-12-06
        • 1970-01-01
        相关资源
        最近更新 更多