正确的设计模式,用于处理对象的多态集合

时间:2011-01-23 10:34:11

标签: c++ polymorphism

假设我有以下类:

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

class Object1: public BaseObject {
    public:
        virtual int getSomeCommonProperty();  // optional
        int getSomeSpecificProperty();
};

class BaseCollection {
    public:
        virtual void someCommonTask();
};

class Collection1: public BaseCollection {
    public:
        virtual void someCommonTask();  // optional
        void someSpecificTask();
};

从BaseCollection派生的每个集合处理特定的对象类型(并且只有一种类型)。但BaseCollection应该能够使用BaseObject中的常见对象属性执行所有对象共有的任务。

目前,我可能会考虑三种解决方案:

1)将对象列表存储在BaseCollection中,例如:

class BaseCollection {
    vector<BaseObject*> objects;
};

这个解决方案的问题在于,当我需要在Collection1中执行特定于对象的任务时,我需要一个dynamic_cast&lt;&gt;,因为我不想对特定属性使用虚拟inherance,只适用于一种类型宾语。考虑到dynamic_cast&lt;&gt;可能会被称为每秒数百万次,这似乎是性能关键应用程序的一个问题。

2)将对象列表存储在Collection1中,例如:

class Collection1: public BaseCollection {
    vector<Object1*> objects;
}

但是我需要一些方法来访问BaseCollection中的这个对象列表,以便能够在它们上执行一些常见任务,理想情况是通过迭代器。我需要创建一个返回BaseCollection向量的函数,但同样,这似乎不是很有效,因为这样做的唯一方法是创建一个新的向量(可能包含数千个对象)...

3)将对象列表存储在BaseCollection AND Collection1:

class BaseCollection {
    public:
        void someCommonTask();  // Use baseObjects
        virtual void addObject() = 0;

    protected:
        vector<BaseObject*> baseObjects;
};

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

两个列表实际上包含相同的对象。这听起来像丑陋吗?

我正在为这类问题寻找合适/正确/最佳的设计模式,上面公开的3个解决方案中没有一个真正满足我......

也许有可能用模板解决这个问题,但是我没有看到存储这样的多态集合列表的方法:

vector<BaseCollection*> collections;

5 个答案:

答案 0 :(得分:2)

您可以通过基类(智能)指针将基类和派生类的所有对象存储在一个集合中。使用visitor design patterndouble dispatch机制,您只能在特定类型的对象上调用函数,而不必在基类接口中公开该函数。例如:

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

输出:

$ ./test
virtual void BaseObject::getSomeCommonProperty()
virtual void Object1::getSomeCommonProperty()
virtual void BaseObject::getSomeCommonProperty()
void Object1::getSomeSpecificProperty()

答案 1 :(得分:0)

我认为你应该选择选项1,而是使用静态转换。在所有派生集合确定知道成员变量的类型之后。

This answer解释得非常好。

答案 2 :(得分:0)

我认为解决方案应该是factory method patterntemplate method pattern的混合。看看那些来改进你的设计。

修改:以下是示例代码。 GenericProductBaseObject,它提供了两种方法,一种是通用的(虽然它可以被覆盖),一种不做任何事情的特定方法,它不是纯虚拟的,所以这个类可以实例化。 SpecificProduct是一个子类,它以某种方式实现特定方法。

现在,Factory类是一个抽象类,它定义了特定工厂创建特定产品的接口,它定义了一个创建产品的纯虚方法createProduct。创建了两个具体工厂GenericFactorySpecificFactory,用于创建特定产品。

最后,Consumer抽象类(对应于代码中的BaseCollection),它定义了一个用于创建工厂createFactory的纯虚方法,以强制子类创建它们拥有混凝土工厂(因此,正确的产品)。该类还定义了一个方法fillArray(原型模式),用工厂创建的产品填充数组。

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

答案 3 :(得分:0)

也许这会在这里诀窍?

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

如果你想在Collection1中抽象容器(比如使用list而不是使用vector),要在Manipulator中使用它,创建一个抽象迭代器......

答案 4 :(得分:0)

Id使用嵌套适配器,如下例所示。你必须专门为每个想要进行花哨更新的课程 !示例有内存泄漏 - 分配的A,B,Q对象不会被删除!

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