Right design pattern to deal with polymorphic collections of objects

后端 未结 5 1046
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-01-22 04:59

Suppose I have the following classes:

class BaseObject {
    public:
        virtual int getSomeCommonProperty();
};

class Object1: public BaseObject {
    publ         


        
相关标签:
5条回答
  • 2021-01-22 05:30

    You can store all your objects of base and derived classes in one collection through the base class (smart) pointer. Using visitor design pattern and double dispatch mechanism you can call a function only on objects of a specific type without having to expose that function in the base class interface. For example:

    #include <boost/intrusive_ptr.hpp>
    #include <boost/bind.hpp>
    #include <vector>
    #include <algorithm>
    #include <stdio.h>
    
    struct Visitor { // Visitor design patter
        virtual void visit(struct BaseObject&) {}
        virtual void visit(struct Object1&) {}
    };
    
    struct BaseObject {
        unsigned ref_count_; // intrusive_ptr support
        BaseObject() : ref_count_() {}
        virtual ~BaseObject() {}
        virtual void accept(Visitor& v) { v.visit(*this); } // Visitor's double dispatch
        virtual void getSomeCommonProperty() { printf("%s\n", __PRETTY_FUNCTION__); }
    };
    
    void intrusive_ptr_add_ref(BaseObject* p) { // intrusive_ptr support
        ++p->ref_count_;
    }
    
    void intrusive_ptr_release(BaseObject* p) { // intrusive_ptr support
        if(!--p->ref_count_)
            delete p;
    }
    
    struct Object1 : BaseObject {
        virtual void accept(Visitor& v) { v.visit(*this); } // Visitor's double dispatch
        virtual void getSomeCommonProperty() { printf("%s\n", __PRETTY_FUNCTION__); }
        void getSomeSpecificProperty() { printf("%s\n", __PRETTY_FUNCTION__); }
    };
    
    template<class T, class Functor>
    struct FunctorVisitor : Visitor {
        Functor f_;
        FunctorVisitor(Functor f) : f_(f) {}
        void visit(T& t) { f_(t); } // apply to T objects only
        template<class P> void operator()(P const& p) { p->accept(*this); }
    };
    
    template<class T, class Functor>
    FunctorVisitor<T, Functor> apply_to(Functor f)
    {
        return FunctorVisitor<T, Functor>(f);
    }
    
    int main()
    {
        typedef boost::intrusive_ptr<BaseObject> BaseObjectPtr;
        typedef std::vector<BaseObjectPtr> Objects;
    
        Objects objects;
        objects.push_back(BaseObjectPtr(new BaseObject));
        objects.push_back(BaseObjectPtr(new Object1));
    
        for_each(
              objects.begin()
            , objects.end()
            , boost::bind(&BaseObject::getSomeCommonProperty, _1)
            );
    
        for_each(
              objects.begin()
            , objects.end()
            , apply_to<BaseObject>(boost::bind(&BaseObject::getSomeCommonProperty, _1))
            );
    
        for_each(
              objects.begin()
            , objects.end()
            , apply_to<Object1>(boost::bind(&Object1::getSomeSpecificProperty, _1))
            );
    }
    

    Output:

    $ ./test
    virtual void BaseObject::getSomeCommonProperty()
    virtual void Object1::getSomeCommonProperty()
    virtual void BaseObject::getSomeCommonProperty()
    void Object1::getSomeSpecificProperty()
    
    0 讨论(0)
  • 2021-01-22 05:36

    I think the solution should be a mix of factory method pattern and template method pattern. Take a look at those to refine your design.

    Edit: Here is a sample code. GenericProduct is the BaseObject, it provides two methods, one that is general (though it could be overridden), and a specific method which does nothing, it is not a pure virtual so this class can be instantiated. SpecificProduct is a subclass, which implements the specific method in some way.

    Now, Factory class is an abstract class that defines an interface for creating specific products by specific factories, it defines a pure virtual method createProduct which creates the product. Two concrete factories are created GenericFactory and SpecificFactory which create specific products.

    Finally, the Consumer abstract class (which corresponds to BaseCollection in your code), it defines a pure virtual method for creating a factory createFactory in order to force subclasses to create their own concrete factories (and hence, the correct products). The class also define a method fillArray (prototype pattern) to fill the array with products created by the factory.

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class GenericProduct{
        public:
            virtual void getSomeCommonProperty()
            {
                cout<<"Common Property\n";
            }
            virtual void getSomeSpecificProperty()
            {
                cout<<"Generic Has Nothing Specific\n";
            }
    };
    
    class SpecificProduct : public GenericProduct{
        public:
            virtual void getSomeSpecificProperty()
            {
                cout<<"Specific Product Has a Specific Property\n";
            }
    };
    
    class Factory
    {
        public:
            virtual GenericProduct* createProduct() = 0;
    };
    
    class GenericFactory : public Factory
    {
        public:
            virtual GenericProduct* createProduct()
            {
                return new GenericProduct();
            }
    };
    
    class SpecificFactory : public Factory
    {
        public:
            virtual GenericProduct* createProduct()
            {
                return new SpecificProduct();
            }
    };
    
    class Consumer
    {
        protected:
            vector<GenericProduct*> gp;
            Factory* factory;
    
        protected:
            virtual void createFactory() = 0;
    
        public: 
            void fillArray()
            {
                createFactory();
                for(int i=0; i<10; i++)
                {
                    gp.push_back(factory->createProduct());
                }
            }
    
            virtual void someCommonTask()
            {
                cout<<"Performaing a Common Task ...\n";
                for(int i=0; i<10; i++)
                {
                    gp[i]->getSomeCommonProperty();
                }
            }
            virtual void someSpecificTask()
            {
                cout<<"Performaing a Specific Task ...\n";
                for(int i=0; i<10; i++)
                {
                    gp[i]->getSomeSpecificProperty();
                }
            }
    };
    
    class GenericConsumer : public Consumer
    {
        virtual void createFactory()
        {
            factory = new GenericFactory();
        }
    };
    
    class SpecificConsumer : public Consumer
    {
        virtual void createFactory()
        {
            factory = new SpecificFactory();
        }
    };
    
    
    int main()
    {
        Consumer* c = new GenericConsumer();
        c->fillArray();
        c->someCommonTask();
        return 0;
    }
    
    0 讨论(0)
  • 2021-01-22 05:43

    Id use nested adapter as in below example. You have to specialize it for every class you want to do a fancy update !The example has memory leak - allocated A, B, Q objects are not deleted!

    #include <iostream>
    #include <vector>
    #include <algorithm>
    class Q
    {
    public:
        virtual void Foo()
        {
            std::cout << "Q::Foo()" << std::endl;
        }
    };
    class A
    {
    
    public:
        virtual void Foo()
        {
            std::cout << "A::Foo()" << std::endl;
        }
    };
    
    class B : public A
    {
    public:
        virtual void Foo()
        {
        std::cout << "B::Foo()" << std::endl;
        }
        virtual void BFoo()
        {
            std::cout << "B::BFoo()" << std::endl;
        }
    };
    
    template <typename ElementType>
    class C
    {
    public:
        template <typename T>
        void add(T* ptr){m_Collection.push_back(std::unique_ptr<Adapter>(new ConcreteAdapter<T>(ptr)));}
        void updateAll()
        {
            std::for_each(m_Collection.begin(), m_Collection.end(), [&](std::unique_ptr<Adapter> &adapter)->void{adapter->update();});
        }
    private:
        class Adapter
        {
        public:
            virtual ElementType* get() = 0;
            virtual void update(){get()->Foo();}
    
        };
        template <typename T>
        class ConcreteAdapter : public Adapter
        {
        public:
            ConcreteAdapter(T* ptr) : m_Ptr(ptr){}
            virtual T* get(){return m_Ptr;}
        protected:
            T* m_Ptr;
        };
    
        template <>
        class ConcreteAdapter<B> : public Adapter
        {
        public:
            ConcreteAdapter(B* ptr) : m_Ptr(ptr){}
            virtual B* get(){return m_Ptr;}
            virtual void update()
            {
            get()->Foo();
            get()->BFoo();
            }
        private:
            B* m_Ptr;
    
        };
        std::vector<std::unique_ptr<Adapter>> m_Collection;
    };
    int main()
    {
        C<A> c;
        c.add(new A());
        c.add(new B());
        //c.add(new Q()); //error - correct
        c.updateAll();
        return 0;
    }
    
    0 讨论(0)
  • 2021-01-22 05:46

    Maybe this will do the trick here ?

    class CollectionManipulator {
    public:
        void someCommonTask(BaseCollection& coll) {
             for(unsigned int i = 0; i < coll.size(); i++)
                  someCommonTask(coll.getObj(i));
        }
    private:
        void someCommonTask(BaseObject*);  // Use baseObjects
    
    };
    
    class BaseCollection {
         friend class CollectionManipulator;
    private:
         virtual BaseObject* getObj(unsigned int) = 0;
         virtual unsigned int size() const = 0;
    };
    
    class Collection1 : public BaseCollection {
        vector<Object1*> objects;
    
    public:
        virtual void addObject() {
            Object1* obj = new Object1;
            objects.push_back(obj);
            baseObjects.push_back(obj);
        }
    
        void someSpecificTask(); // Use objects, no need of dynamic_cast<>
    
    private:
        BaseObject* getObj(unsigned int value) {
            return object[value];
        }
    
        unsigned int size() const {
            return objects.size();
        }
    }
    

    If you want abstract your container in Collection1 (like using list instead using vector), to use it in Manipulator, create an abstract iterator...

    0 讨论(0)
  • 2021-01-22 05:46

    I think you should go for option 1 but use a static cast instead. After all the derived collection knows the type of the member variable for sure.

    This answer explains it very well.

    0 讨论(0)
提交回复
热议问题