C ++ дизайн: сокращение связи в глубокой иерархии классов

0

Пожалуйста, рассмотрите эту иерархию классов:

#include <iostream>

struct Base
{    
    Base(int arg1, int arg2, int arg3, int arg4)
    {
        std::cout << "Base::Base:" << arg1 << "," << arg2 << "," << arg3 << "," << arg4 << std::endl;
    }
    void BaseDoWork()
    {
        std::cout << "Base::BaseDoWork" << std::endl;
    }
};

struct Derived1:public Base
{
    Derived1(int arg1, int arg2, int arg3, int arg4):Base(arg1, arg2, arg3, arg4){}
    void DerivedDoWork()
    {
        BaseDoWork();
    }
};

struct Derived2:public Derived1
{
    Derived2(int arg1, int arg2, int arg3, int arg4):Derived1(arg1, arg2, arg3, arg4){}
};

int main(int argc, char *argv[])
{
    Derived2 myDerived2(1,2,3,4);
    myDerived2.DerivedDoWork();
    return 0;
}

Класс Derived1 имеет больше связей, чем хотелось бы: он должен знать, как построить класс "Base". В глубокой иерархии это означало бы распространение аргументов для конструктора класса Base, вплоть до иерархии классов. Для изменения конструктора класса "Base" потребуется изменение для всех производных классов.

Мои цели для этой иерархии классов:

  1. Derived1 ожидает метод BaseDoWork() в базовом классе, но ему не нужно знать, как построить базовый класс.

  2. Derived2 знает, как построить класс "Base".

В идеале я хотел бы сделать Derived1 шаблоном, принимающим любой базовый класс с доступным методом "BaseDoWork()" в следующих строках:

template <T> struct Derived1:public T
{
    void DerivedDoWork()
    {
        BaseDoWork();
    }
};

Но если я Derived1<Base> этот шаблон как Derived1<Base> я не вижу, как построить класс "Base", не добавляя знания аргументов конструктора "Base" в Derived1.

Теги:
design

2 ответа

1
Лучший ответ

Предполагая, что это реальная иерархия, если Derived1 является Base производным классом, он должен знать, как конструировать родительский класс (Base).

Как я понял, может быть, я ошибаюсь, если Derived1 не является Base производным (дочерним) классом и ему нужно только знать его интерфейс, нет необходимости знать, как построить экземпляр Base. Таким образом, вы можете определить Derived1 следующим образом:

struct Derived1
{
    Derived1(int arg1, int arg2, int arg3, int arg4)
    {}

    .....
};

Только Base классы должны знать, как конструировать Base:

struct Derived2 : public Base
{
    Derived2(int arg1, int arg2, int arg3, int arg4) : 
        Base(arg1, arg2, arg3, arg4)
    {}

    .....
};

Теперь, если Derived2 действительно является Derived1, он должен знать, как его создать. Предыдущий код стал:

struct Derived2 : public Base, public Derived1
{
    Derived2(int arg1, int arg2, int arg3, int arg4) : 
        Base(arg1, arg2, arg3, arg4),
        Derived1(arg1, arg2, arg3, arg4)
    {}

    .....
};

Если вы используете метод Base::BaseDoWork() из Derived1, вы можете определить классы следующим образом:

struct Base
{    
    Base(int arg1, int arg2, int arg3, int arg4)
    {
        std::cout << "Base::Base:" << arg1 << "," << arg2 << "," << arg3 << "," << arg4 << std::endl;
    }

    virtual void BaseDoWork()
    {
        std::cout << "Base::BaseDoWork" << std::endl;
    }
};

struct Derived1
{
    Derived1(int arg1, int arg2, int arg3, int arg4)
    {}

    template <typename T>
    void DerivedDoWork(T & base)  // only knows T interface
    {
      base.BaseDoWork();
    }
};

struct Derived2 : public Base, public Derived1
{
    Derived2(int arg1, int arg2, int arg3, int arg4) : 
      Base(arg1, arg2, arg3, arg4),
      Derived1(arg1, arg2, arg3, arg4)
    {}

    virtual void DerivedDoWork()
    {
      Derived1::DerivedDoWork<Base>(*(static_cast<Base *>(this)));
    }
};

Главная хранит то же самое.

Возможно, вам потребуется просмотреть эту иерархию.

  • 0
    Да, это, наверное, самое простое решение. Я удалил список аргументов из конструктора Derived1, потому что отсоединение Derived1 от этого списка аргументов было одной из главных целей. И методы DoWork () не обязательно должны быть виртуальными.
1

Когда вы делаете public наследование Derived1 из Base, результат заключается в том, что Derived1 уже знает, как построить Base, поэтому, если вы хотите, чтобы Derived1 не знал, как построить Base, может быть, лучше не наследовать его от Base? Вместо этого отделите Base как своего рода исполнителя.

Рассмотрите этот фрагмент кода, я не утверждаю, что это решение, но, возможно, оно подтолкнет вас к другим мыслям (имена классов одинаковы):

#include <iostream>

struct Base
{
    Base()
    {
        std::cout << "Base::Base:" << std::endl;
    }
    Base(int arg1, int arg2, int arg3, int arg4)
    {
        std::cout << "Base::Base:" << arg1 << "," << arg2 << "," << arg3 << "," << arg4 << std::endl;
    }
    void DoWork()
    {
        std::cout << "Base::BaseDoWork" << std::endl;
    }
};

template <typename T>
struct Derived1
{
    T object;

    Derived1(T obj){ object = obj; }
    void DerivedDoWork()
    {
        object.DoWork();
    }
};

template <typename T>
struct Derived2 : public Derived1<T>
{
    Derived2(int arg1, int arg2, int arg3, int arg4) : Derived1( T(arg1, arg2, arg3, arg4))
    { }
};

int main(int argc, char *argv[])
{
    Derived2<Base> myDerived2(1,2,3,4);
    myDerived2.DerivedDoWork();
    return 0;
}
  • 0
    Мне не приходило в голову хранить фактический объект в Derived1. Насколько я вижу, конструктор по умолчанию, который вы добавили для Base, никогда не вызывается: я пропускаю использование для этого?
  • 0
    @SimonElliott, конструктор по умолчанию для Base вызывается один раз при Derived1 объекта Derived1 , я просто добавил конструктор по умолчанию, чтобы сделать объект Base членом Derived1 . Вы можете заменить T object на T& object , const T& object или T* object , const T* object и инициализировать эту ссылку / указатель в конструкторе Derived1 , чтобы Base не был членом Derived1
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню