ООП Стиль кодирования: инициализировать все на конструкторе?

14

Я все еще считаю себя учеником-программистом, поэтому я всегда стремлюсь найти «лучший» способ для типичного программирования. Сегодня мой коллега утверждал, что мой стиль кодирования выполняет какую-то ненужную работу, и я хочу услышать мнения других. Как правило, когда я проектирую класс на языке ООП (обычно C ++ или Python), я делю инициализацию на две разные части:

class MyClass1 {
public:
    Myclass1(type1 arg1, type2 arg2, type3 arg3);
    initMyClass1();
private:
    type1 param1;
    type2 param2;
    type3 param3;
    type4 anotherParam1;
};

// Only the direct assignments from the input arguments are done in the constructor
MyClass1::myClass1(type1 arg1, type2 arg2, type3 arg3)
    : param1(arg1)
    , param2(arg2)
    , param3(arg3)
    {}

// Any other procedure is done in a separate initialization function 
MyClass1::initMyClass1() {
    // Validate input arguments before calculations
    if (checkInputs()) {
    // Do some calculations here to figure out the value of anotherParam1
        anotherParam1 = someCalculation();
    } else {
        printf("Something went wrong!\n");
        ASSERT(FALSE)
    }
}

(или, эквивалент Python)

class MyClass1:

    def __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
        #optional
        self.anotherParam1 = None

    def initMyClass1():
        if checkInputs():
            anotherParam1 = someCalculation()
        else:
            raise "Something went wrong!"

Что вы думаете об этом подходе? Должен ли я воздерживаться от разделения процесса инициализации? Этот вопрос не ограничивается только C ++ и Python, и ответы на другие языки также приветствуются.

Caladbolgll
источник
1
связанные: stackoverflow.com/questions/3127454/…
Док Браун
2
см. также: stackoverflow.com/questions/33124542/…
Док Браун
1
А для Python: stackoverflow.com/questions/20661448/…
Док Браун
Почему ты обычно это делаешь? Привычка? Вам когда-нибудь давали повод сделать это?
JeffO
@JeffO У меня появилась эта привычка, когда я работал над созданием GUI с библиотекой MFC. Большинство классов, связанных с пользовательским интерфейсом, таких как CApp, CWindow, CDlg и т. Д., Имеют функции OnInit (), которые вы можете перезаписывать, которые отвечают на их соответствующие сообщения.
Caladbolgll

Ответы:

28

Хотя иногда это проблематично, есть много преимуществ для инициализации всего в конструкторе:

  1. Если возникает ошибка, это происходит как можно быстрее и ее проще всего диагностировать. Например, если нулевое значение является недопустимым значением аргумента, проверьте и завершите с ошибкой в ​​конструкторе.
  2. Объект всегда находится в действительном состоянии. Коллега не может ошибиться и забыть позвонить, initMyClass1()потому что его там нет . «Самые дешевые, быстрые и надежные компоненты - это те, которых нет».
  3. Если это имеет смысл, объект можно сделать неизменным, что имеет много преимуществ.
user949300
источник
2

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

Зачем делить то, что можно сделать одним выстрелом, на два?

Дополнительная инициализация - это просто нечто дополнительное для программистов, использующих ваш API, для запоминания, и предоставляет больше возможностей для неправильной работы, если они не делают это правильно, но для чего им это стоит за эту дополнительную нагрузку?

Вы хотите предоставить мертвую простую, удобную в использовании, сложную ошибочную абстракцию. Программирование достаточно сложно без каких-либо ненужных вещей, которые нужно помнить / обручи, чтобы прыгать. Вы хотите, чтобы ваши пользователи API (даже если вы используете собственный API) попали в пропасть успеха .

Эрик Эйдт
источник
1

Инициализируйте все, кроме области больших данных. Инструменты статического анализа помечают поля, не инициализированные в конструкторе. Однако наиболее продуктивный / безопасный способ - иметь все переменные-члены с конструкторами по умолчанию и явно инициализировать только те, которые требуют инициализации не по умолчанию.

zzz777
источник
0

Есть случаи, когда объект имеет большую инициализацию, которую можно разделить на две категории:

  1. Атрибуты, которые являются неизменяемыми или не требуют сброса.

  2. Атрибуты, для которых может потребоваться возврат к исходным значениям (или шаблонным значениям) на основе некоторого условия после выполнения их работы, что-то вроде мягкого сброса. например, соединения в пуле соединений.

Здесь вторая часть инициализации, хранящаяся в отдельной функции, скажем InitialiseObject (), может быть вызвана в ctor.

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

Рамакант
источник
0

Как уже говорили другие, обычно хорошей идеей является инициализация в конструкторе.

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

Обработка ошибок

Во многих языках единственный способ сообщить об ошибке в конструкторе - вызвать исключение.

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

Вероятно, наиболее распространенным примером этого является C ++, если стандарт проекта / организации должен отключать исключения.

Государственный аппарат

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

В этом случае, как правило, конструкция (и удаление) объекта имеют дело только с ориентированными на память атрибутами (именем файла, портом и т. Д.). Затем появятся функции для конкретного управления переходами состояний, например, открытие, закрытие, которые фактически являются функциями инициализации и удаления.

Преимущества заключаются в обработке ошибок, как и выше, но также может быть случай отделения конструкции от инициализации (скажем, вы строите вектор файлов и открываете их асинхронно).

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

Alex
источник