Регистрация производных классов в C ++ - PullRequest
2 голосов
/ 23 декабря 2008

РЕДАКТИРОВАТЬ: мелкие исправления (виртуальная печать; вернуть mpInstance) после замечаний в ответах.

Я пытаюсь создать систему, в которой я могу получить дочерний класс из любого базового класса, и его реализация должна заменить реализацию базового класса.

Все объекты, которые создают и используют объекты базового класса, не должны изменять способ, которым они создают или вызывают объект, т.е. должны продолжать вызывать BaseClass.Create (), даже когда они фактически создают класс 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 ]

2 голосов
/ 23 декабря 2008

Этот тип паттерна довольно распространен. Я не эксперт по C ++, но в Java вы видите это везде. Динамическое приведение представляется необходимым, потому что компилятор не может сказать, какую фабрику вы сохранили на карте. Насколько мне известно, с нынешним дизайном мало что можно сделать по этому поводу. Было бы полезно узнать, как эти объекты предназначены для использования. Позвольте мне привести пример того, как подобная задача выполняется в библиотеке баз данных Java (JDBC):

В системе есть DriverManager, который знает о драйверах JDBC. Водители должны быть как-то зарегистрированы (детали не важны); после регистрации каждый раз, когда вы запрашиваете соединение с базой данных, вы получаете объект Connection. Обычно этот объект будет OracleConnection или MSSQLConnection или чем-то подобным, но клиентский код видит только «Соединение». Чтобы получить объект Statement, вы говорите connection.prepareStatement, который возвращает объект типа PreparedStatement; за исключением того, что это действительно OraclePreparedStatement или MSSQLPreparedStatement. Это прозрачно для клиента, поскольку фабрика для операторов находится в соединении, а фабрика для соединений - в DriverManager.

Если ваши классы связаны друг с другом, вы можете захотеть иметь функцию, которая возвращает определенный тип класса, так же как метод getConnection DriverManager возвращает 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(); }
}

Что касается других комментариев, критикующих использование наследования: по моему мнению, нет никакой разницы между интерфейсом и публичным наследованием; так что продолжайте и используйте классы вместо интерфейсов везде, где это имеет смысл. Чистые интерфейсы могут быть более гибкими в долгосрочной перспективе, но, возможно, нет. Без подробностей о вашей иерархии классов невозможно сказать.

1 голос
/ 23 декабря 2008

Обычно шаблон базового класса / производного класса используется, когда у вас есть интерфейс в базовом классе, и этот интерфейс реализован в производном классе (отношение 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;

};

Как правило, вы смешиваете наследование, виртуальные функции и шаблоны так, как их не следует смешивать.

0 голосов
/ 23 декабря 2008

Возможно, я ошибаюсь, но я не нашел никакого выражения return в вашем методе CBaseClass :: Create ()!

0 голосов
/ 23 декабря 2008

Лично я думаю, что этот дизайн использует наследование.

«Я пытаюсь создать систему, в которой я могу получить дочерний класс из любого базового класса, и его реализация должна заменить реализацию базового класса». - Я не знаю, что отношения 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(); }
}

Теперь я могу при необходимости изменить поведение, изменив реализацию Foo, которую я передаю MixinDemo.

0 голосов
/ 23 декабря 2008

Не прочитав весь код или не углубившись в детали, кажется, что вы должны были сделать следующее:

  • make b типа CChildClass,
  • делает CBaseClass::Print виртуальной функцией.
...