在C ++中注册派生类

时间:2008-12-23 13:36:29

标签: c++ design-patterns templates abstract-class factory

编辑:小答案(虚拟打印;返回mpInstance)后面的答案备注。

我正在尝试创建一个系统,我可以从任何Base类派生一个Child类,它的实现应该替换基类的实现。

创建和使用基类对象的所有对象都不应该改变它们创建或调用对象的方式,即即使它们实际创建了一个Child类,也应该继续调用BaseClass.Create()。 Base类知道它们可以被重写,但是它们不知道覆盖它们的具体类。

我希望所有Child课程的注册都在一个地方完成。

这是我的实施:

class CAbstractFactory
{
public:
    virtual ~CAbstractFactory()=0;
};

template<typename Class>
class CRegisteredClassFactory: public CAbstractFactory
{
public:
    ~CRegisteredClassFactory(){};
    Class* CreateAndGet()
    {
        pClass = new Class;
        return pClass;
    }
private:
    Class* pClass;
};

// holds info about all the classes that were registered to be overridden
class CRegisteredClasses
{
public:
    bool find(const string & sClassName);
    CAbstractFactory* GetFactory(const string & sClassName)
    {
        return mRegisteredClasses[sClassName];
    }
    void RegisterClass(const string & sClassName, CAbstractFactory* pConcreteFactory);
private:
    map<string, CAbstractFactory* > mRegisteredClasses;

};


// Here I hold the data about all the registered classes. I hold statically one object of this class.
// in this example I register a class CChildClass, which will override the implementation of CBaseClass, 
// and a class CFooChildClass which will override CFooBaseClass
class RegistrationData
{
    public:
        void RegisterAll()
        {
            mRegisteredClasses.RegisterClass("CBaseClass", & mChildClassFactory);
            mRegisteredClasses.RegisterClass("CFooBaseClass", & mFooChildClassFactory);
        };
        CRegisteredClasses* GetRegisteredClasses(){return &mRegisteredClasses;};
    private:    
        CRegisteredClasses mRegisteredClasses;
        CRegisteredClassFactory<CChildClass> mChildClassFactory;
        CRegisteredClassFactory<CFooChildClass> mFooChildClassFactory;
};

static RegistrationData StaticRegistrationData;

// and here are the base class and the child class 
// in the implementation of CBaseClass::Create I check, whether it should be overridden by another class.
class CBaseClass
{
public:
    static CBaseClass* Create()
    {
        CRegisteredClasses* pRegisteredClasses = StaticRegistrationData.GetRegisteredClasses();
        if (pRegisteredClasses->find("CBaseClass"))
        {
            CRegisteredClassFactory<CBaseClass>* pFac = 
                dynamic_cast<CRegisteredClassFactory<CBaseClass>* >(pRegisteredClasses->GetFactory("CBaseClass"));

            mpInstance = pFac->CreateAndGet();
        }
        else
        {
            mpInstance = new CBaseClass;
        }
        return mpInstance;
    }
    virtual void Print(){cout << "Base" << endl;};
private:
    static CBaseClass* mpInstance;

};

class CChildClass : public CBaseClass
{
public:
    void Print(){cout << "Child" << endl;};
private:

};

使用此实现,当我从其他类中执行此操作时:

StaticRegistrationData.RegisterAll();
CBaseClass* b = CBaseClass::Create();
b.Print();

我希望在输出中得到“Child”。

您如何看待这种设计?我把事情弄得太复杂了,可以做得更容易吗?我是否可以创建一个继承自抽象类的模板?

我不得不使用dynamic_pointer(否则没有编译) - 这是否暗示出错了?

谢谢。

5 个答案:

答案 0 :(得分:2)

这种模式相当普遍。我不是C ++专家,但在Java中,你到处都看到了这一点。动态转换似乎是必要的,因为编译器无法分辨您在地图中存储的工厂类型。据我所知,使用当前设计,你无能为力。了解如何使用这些对象将有所帮助。让我举一个例子,说明如何在Java的数据库库(JDBC)中完成类似的任务:

系统有一个DriverManager,它知道JDBC驱动程序。司机必须以某种方式注册(细节并不重要);一旦您要求数据库连接注册,您就会获得一个Connection对象。通常,此对象将是OracleConnection或MSSQLConnection或类似的东西,但客户端代码只能看到“连接”。要获取Statement对象,请使用connection.prepareStatement,它返回PreparedStatement类型的对象;除了它确实是OraclePreparedStatement或MSSQLPreparedStatement。这对客户端是透明的,因为Statements的工厂位于Connection中,而Connections的工厂位于DriverManager中。

如果您的类具有相似的相关性,您可能希望拥有一个返回特定类型类的函数,就像DriverManager的getConnection方法返回一个Connection一样。无需铸造。

您可能需要考虑的另一种方法是使用工厂,该工厂对您需要的每个特定类都有工厂方法。然后你只需要一个工厂工厂来获得工厂的实例。示例(对不起,如果这不是正确的C ++):

class CClassFactory
{
  public:
    virtual CBaseClass* CreateBase() { return new CBaseClass(); }
    virtual CFooBaseClass* CreateFoo() { return new CFooBaseClass();}
}

class CAImplClassFactory : public CClassFactory
{
  public:
    virtual CBaseClass* CreateBase() { return new CAImplBaseClass(); }
    virtual CFooBaseClass* CreateFoo() { return new CAImplFooBaseClass();}
}

class CBImplClassFactory : public CClassFactory // only overrides one method
{
  public:
    virtual CBaseClass* CreateBase() { return new CBImplBaseClass(); }
}

至于批评使用继承的其他评论:在我看来,界面和公共继承之间没有区别;所以继续使用类而不是接口,只要它有意义。从长远来看,Pure Interfaces可能更灵活,但可能不是。如果没有关于类层次结构的更多细节,则无法说出来。

答案 1 :(得分:1)

通常,在基类中有接口时使用基类/派生类模式,并且该接口在派生类(IS-A关系)中实现。在您的情况下,基类似乎与派生类没有任何关联 - 它也可能是无效的。*

如果基类和派生类之间没有连接,为什么要使用继承?如果工厂的输出不能以一般方式使用,拥有工厂有什么好处?

class CAbstractFactory
{
public:
    virtual ~CAbstractFactory()=0;
};

这是完全错误的。工厂必须制造可以立即使用的东西:

class CAbstractFactory
{
public:
    virtual ~CAbstractFactory(){};
public:
    CBaseClass* CreateAndGet()
    {
        pClass = new Class;
        return pClass;
    }
private:
    CBaseClass* pClass;

protected:
    CBaseClass *create() = 0;

};

一般来说,你将继承,虚函数和模板混合在一起,不应该混合它们。

答案 2 :(得分:0)

如果没有阅读所有代码或进入细节,您似乎应该完成以下操作:

  • 制作CChildClass类型的b
  • 使CBaseClass::Print成为虚函数。

答案 3 :(得分:0)

也许我错了,但我没有在你的CBaseClass :: Create()方法中找到任何return语句!

答案 4 :(得分:0)

就个人而言,我认为这种设计过度使用了继承。

“我正在尝试创建一个系统,我可以从任何Base类派生一个Child类,它的实现应该替换基类的实现。” - 我不知道IS-A关系应该那么灵活。

我想知道你是否会更好地使用接口(C ++中的纯虚拟类)和mixin行为。如果我用Java编写它,我会这样做:

public interface Foo
{
    void doSomething();
}

public class MixinDemo implements Foo
{
    private Foo mixin;

    public MixinDemo(Foo f)
    {
        this.mixin = f;
    }

    public void doSomething() { this.mixin.doSomething(); }
}

现在我可以通过更改传递给MixinDemo的Foo实现来根据需要更改行为。