Шаблоны для создания производного класса, содержащего много полей как в самом себе, так и в базовом классе - PullRequest
6 голосов
/ 21 декабря 2011

Учитывая следующую ранее существующую среду, мне нужно найти хорошие шаблоны проектирования для создания различных экземпляров производного класса.

Основные проблемы, с которыми я сталкиваюсь, следующие:

challenge-1 > Каждый класс имеет более 10 полей и как передать эти поля в производный класс, а затем эффективно в базовый класс.

Чтобы решить эту проблему, я могу найти четыре решения, нони один из них не привлекателен для меня.

Метод 1 > передать все параметры в простом формате

classA::classA(int field1, float field2, ..., double field29)

=> минусы: не стоит создаватьфункция с более чем 6 ~ 7 передаваемыми параметрами

Метод 2 > передать все параметры как структуру

struct DataClassA
{
int field1;
float field2;
...
double field29;
};

struct DataClassBA : DataClassA
{
int    m_iField30;
// ...
double m_iField40;
};

Итак, сначала я передаю DataClassBA в classBA, а затем по очереди classBA передать DataClassA на classA.=> cons: type DataClassBA и classBA являются схожими типами, за исключением того, что один содержит операции, а другой нет.Кроме того, при передаче структуры конструкторам, существует штраф за копии и дубликаты.Представьте, что для каждого отдельного класса мы должны определить аналогичную структуру для хранения всех данных инициализации.Ключевым отличием между классом и его соответствующей структурой является то, что класс содержит некоторые методы, в то время как структура используется исключительно для передачи данных.

Метод 3 > установить все поля с помощью функций Set

classA
{
public:
    int Field1() const { return m_iField1; }
    classA& Field1(int field1)
    {
      m_iField1 = field1;
      return *this;
    }
    ...
}

classBA : public classA
{
public:
    int Field30() const { return m_iField30; }
    classBA& Field30(int field30)
    {
      m_iField30 = field30;
      return *this;
    }
    ...
}

=> минусы: каждое создание экземпляра вызовет много вызовов функций и будет очень дорогим.

Метод 4 > передает карту всем конструкторам базового и производного класса.

=> минусы: я действительно считаю, что это плохая идея, хотя она упрощает передачу данных.

challenge-2 > Значение по умолчанию для базового класса определяетсяего другой производный класс.Например, значение по умолчанию classA::m_iField2 отличается в зависимости от другого производного класса.

Чтобы решить эту проблему, я могу найти два решения, но ни одно из них мне не подходит.

Метод 1 > Добавить логику по умолчанию к самому производному классу.

Метод 2 > Добавить логику по умолчанию к самому классу фабрики.

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

спасибо

/////////////////////// framework ////////////////////////////////////////
// Note: 
// <1> the class hierarchy has to kept as this
// <2> getter and setter functions in each class have to kept as this
// <3> add new functions(i.e constructors) are allowed
// <4> add new classes or structures are allowed
/////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include <map>
#include <string>
#include <iostream>
using namespace std;

/************************************************************************/
/* Class Name: classA (an abstract base class)
 * default value of m_iField2 is determined by its derived class
/************************************************************************/
class classA
{
public:
    virtual ~classA() = 0 {}
    // ...
private: // 
    int m_iField1;
    float m_iField2;  // one of the potential field that has to get the default value
    // ...
    double m_iField29;
};
/************************************************************************/
/* Class Name: classBA
 * If the pass-in parameters do NOT include value for the field classA::m_iField2
 * then assign its value as 200.0f
/************************************************************************/
class classBA : public classA
{
    // ...
private:
    int    m_iField30;
    // ...
    double m_iField40;
};

/************************************************************************/
/* Class Name: classCA
 * If the pass-in parameters do NOT include value for the field classA::m_iField2
 * then assign its value as 300.0f
/************************************************************************/
class classCA : public classA
{
    // ...
private:
    int m_iField50;
    // ...
    int m_iField60;
};

int main(int argc, char* argv[])
{
    map<string, string> mapStrsBA;
    mapStrsBA["name"] = "classBA";
    mapStrsBA["field1"] = "5";
    // ...
    mapStrsBA["field40"] = "1.89";
    // pass mapStrsBA to a factory class with function to create a new instance of class classBA

    map<string, string> mapStrsCA;
    mapStrsBA["name"] = "classCA";
    mapStrsBA["field1"] = "6";
    // ...
    mapStrsBA["field60"] = "19";
    // pass mapStrsCA to a factory class with function to create a new instance of class classCA
    return 0;
}

Ответы [ 8 ]

7 голосов
/ 11 января 2012

Позвольте мне добавить метод 5 к вашему заданию 1, который не всегда, но часто применим, особенно , если у вас много полей: найдите поля-члены, которые строго принадлежат друг другу, и соберите их влогические объекты.Например, предположим, что у вас есть класс Shape, который выглядит следующим образом:

class Shape
{
public:
  Shape(pass initial values for all member variables);
  // ...
private:
  // bounding box coordinates
  int xmin, xmax;
  int ymin, ymax;
  // color
  int red, green, blue;
  int alpha;
  // center point (for rotations)
  int cx, cy;
};

Это 10 переменных.Тем не менее, они на самом деле не связаны.Большинство из них - пары х / у, а затем есть набор, определяющий цвет.Таким образом, вы можете переписать это следующим образом:

struct Point
{
  int x, y;
  Point(int ax, int ay): x(ax), y(ax) {}
};

struct Color
{
  int red, green, blue, alpha;
  Color(int r, int g, int b, int a): red(r), green(g), blue(b), alpha(a) {}
};

class Shape
{
public:
  // ...
private:
  // bounding box
  Point lower_left, upper_right;
  Color color;
  Point center;
};

Теперь внезапно у вас есть только четыре переменные для передачи.Можно даже подумать о создании типа прямоугольника, состоящего из двух угловых точек, и его использовании для ограничивающего прямоугольника, что приведет к дальнейшему сокращению числа переменных до 3.

Обратите внимание, что не только уменьшается число параметров, которые необходимо передать,это также добавляет ясности.

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


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

2 голосов
/ 21 декабря 2011

Для вызова 1, я думаю, способ 2 лучше. Я не знаю, что означают твои минусы. Даже если вы передаете значение по параметрам, вам все равно нужно скопировать значение в класс член. Struct только делает ваш конструктор простым. И я думаю, что вам не нужно наследство. Как насчет:

struct DataClassA
{
    int field1;
    float field2;
    ...
    double field29;
};

struct DataClassBA
{
    DataClassA a;
    int    m_iField30;
    // ...
    double m_iField40;

};

Для вызова 2, я думаю, вы можете установить значение по умолчанию в структуре данных. И вы измените значение, если вы не хотите значение по умолчанию. Например:

DataClassA::DataClassA()
{
    field1 = 1;
}

DataClassBA::DataClassBA()
{
    a.filed1 = 2;
}
1 голос
/ 17 января 2012

Сначала я отвечу на простой вопрос.;) Для вызова №2 вы должны реализовать логику по умолчанию в каждом производном классе.Основная причина этого заключается в том, что вы можете позже добавить новые производные классы с другой логикой, не касаясь какого-либо кода, кроме самого производного класса.Он инкапсулирует вашу логику.

Для задания № 1 ... Если бы это был я, я бы создал структуру, содержащую значения для базового класса.Затем я создал бы структуру для производного класса, которая содержит только поля, специфичные для производного класса, а также один элемент типа родительской структуры.

struct BaseStruct {
   int BaseValue;
}
struct DerivedA {
   BaseStruct BaseData;
   int DerivedField;
}

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

1 голос
/ 17 января 2012

Я хотел бы использовать «межсетевой экран компиляции», чтобы сделать поля объекта из бизнес-объекта.

struct classAObject
{
    int field1;
    //...
    int field20;
};
struct classBObject : public classAObject
{
    int field30;
    //...
    int field50;
};

class classA
{
   public:
   classA(classAObject* impl) {pImplA = impl;}
   private:
   classAObject* pImplA;
};
class classB : public classA
{
   public:
   classB(classBObject* impl):classA(impl) {pImplB = impl;}
   private:
   classBObject* pImplB;
};

тогда вы могли бы использовать его.

1 голос
/ 11 января 2012

Для первого испытания я бы порекомендовал метод 3, объявив ваши методы set как встроенные (ваш компилятор может сделать это в любом случае, однако это никогда не помешает дать подсказку).Кроме того, в таких случаях кажется ненужным возвращать значение, я бы написал:

inline void Field30(int field30)
{
  m_iField30 = field30;
}

Это разумная отправная точка для построения других методов, учитывая, что все они в основном являются расширениями надпростой шаблон get / set.Кон, который вы перечислили, кажется мне преждевременной оптимизацией.

Задача 2 легко решается путем объявления открытого или защищенного конструктора в базовом классе, который принимает значение по умолчанию в качестве аргумента, и вызова этого конструктора вСписок инициализации производного класса, например, так:

class Base
{
private:

    int Value;

protected:

    Base(int value) :
        Value(value)
    {
        // Do nothing.
    }
};

class Derived : public Base
{
public:

    // A default constructor, that sets the base class default value.
    Derived() :
        Base(5)
    {
        // Do nothing.
    }
};

Объявляя конструктор защищенным и не предоставляя значение по умолчанию, вы также предотвращаете создание экземпляров базы, которые не наследуются производным классом.

0 голосов
/ 15 января 2012

Рассматривали ли вы шаблон шаблона - http://en.wikipedia.org/wiki/Template_method_pattern

0 голосов
/ 11 января 2012

Старайтесь думать об этой проблеме не более абстрактно, чем структуры и классы. То, что вы делаете здесь - это передача конфигурации (или параметров инициализации, если хотите) объекту. Вы можете передавать их как отдельные параметры (метод 1), как один большой объект конфигурации (метод 2) или как несколько специализированных наборов конфигурации (решение celtschk). Вы можете использовать шаблон Builder, чтобы помочь вам создать объект (ы) конфигурации.

Если возможно, лучше всего передать конфигурацию в конструкторе, поскольку она позволяет вам определять элементы как const, ограничивая таким образом возможные состояния, в которых может находиться объект (поэтому избегайте метода 3). Всегда определяйте параметры явно, код легче понять и рефакторинг (поэтому избегайте метода 4). И если у вас есть выбор, отдавайте предпочтение композиции, а не наследованию (как и Шонон).

0 голосов
/ 11 января 2012

вызов 1: Я бы выбрал способ 2. Вы должны поместить все поля в одну структуру и инициализировать только те поля, которые относятся к конкретному конструктору.

вызов2:

Реализуйте его в производном классе.

Следующий пример демонстрирует вышеуказанные решения:

#include <string>
#include <iostream>
using namespace std;

struct InitializationData
{
    // for the base class
    int m_iField1;
    //float m_iField2;  // one of the potential field that has to get the default value
    // ...
    double m_iField29;

    // for the classBA
    int    m_iField30;
    // ...
    double m_iField40;

    // for the classCA
    int m_iField50;
    // ...
    int m_iField60;
};

class classA
{
public:
    classA( const InitializationData &data,
            const float m_iField2_ ):
        m_iField1( data.m_iField1 ),
        m_iField2( m_iField2_ ),
        m_iField29( data.m_iField29 )
    {
    }
    virtual ~classA() {}
    // ...
private: //
    int m_iField1;
    float m_iField2;  // one of the potential field that has to get the default value
    // ...
    double m_iField29;
};

class classBA : public classA
{
public:
    classBA( const InitializationData &data ):
        classA( data, 0.1 ),
        m_iField30( data.m_iField30 ),
        m_iField40( data.m_iField40 )
    {
    }
    virtual ~classBA() {}
private:
    int    m_iField30;
    // ...
    double m_iField40;
};

class classCA : public classA
{
public:
    classCA( const InitializationData &data ):
        classA( data, 0.3 ),
        m_iField50( data.m_iField50 ),
        m_iField60( data.m_iField60 )
    {
    }
    virtual ~classCA() {}
private:
    int m_iField50;
    // ...
    int m_iField60;
};

classA* Create( const InitializationData &data, const int type )
{
    switch ( type )
    {
        case 0 :
            return new classBA( data );
            break;
        case 1 :
            return new classCA( data );
            break;
        default :
            ;
    }

    return nullptr;
}

int main(int argc, char* argv[])
{
    InitializationData data;

    // initialize data
    data.m_iField1 = 1;
    data.m_iField29 = 1;

    data.m_iField30 = 30;
    data.m_iField40 = 40.0;

    data.m_iField50 = 50;
    data.m_iField60 = 60;

    // create the object of specific type
    auto obj = Create( data, 1 );
    // use obj
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...