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


14

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

(або еквівалент пітона)

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, і відповіді для інших мов також високо оцінені.



2
дивіться також: stackoverflow.com/questions/33124542/…
Doc Brown


Чому ти зазвичай це робиш? Звичка? Вам коли-небудь давали привід це зробити?
JeffO

@JeffO Цю звичку я отримав, коли працював над створенням графічного інтерфейсу з бібліотекою MFC. Більшість класів, що стосуються інтерфейсу, такі як CApp, CWindow, CDlg тощо, мають функції OnInit (), з якими можна перезаписати, що відповідає на їх відповідні повідомлення.
Caladbolgll

Відповіді:


28

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

  1. Якщо буде помилка, це відбувається якомога швидше і найпростіше діагностувати. Наприклад, якщо null є недійсним значенням аргументу, протестуйте і помиліться в конструкторі.
  2. Об'єкт завжди знаходиться у дійсному стані. Співробітник не може помилитися і забув зателефонувати, initMyClass1()бо його там немає . "Найдешевші, найшвидші та найнадійніші компоненти - це ті, яких там немає".
  3. Якщо це має сенс, об'єкт можна зробити незмінним, що має масу переваг.

2

Подумайте про абстракцію, яку надаєте своїм користувачам.

Навіщо розділяти щось, що можна зробити одним пострілом на два?

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

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


1

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


0

Є випадки, коли об’єкт має багато ініціалізації, який можна розділити на дві категорії:

  1. Атрибути, які є незмінними або не потребують скидання.

  2. Атрибути, які, можливо, потребують повернення до початкових значень (або шаблонованих значень), виходячи з певної умови після виконання своєї роботи, типу м'якого скидання. наприклад, з'єднання в пулі з'єднань.

Тут друга частина ініціалізації, що зберігається в окремій функції, скажімо InitialiseObject (), може бути викликана в ctor.

Ця ж функція може бути викликана пізніше, якщо потрібне м'яке скидання, без необхідності скидання та відтворення об'єкта.


0

Як вже говорили інші, зазвичай ідеально ініціалізувати в конструкторі.

Однак є причини, які не можуть застосовуватись у певних випадках.

Помилка обробки

У багатьох мовах єдиний спосіб сигналізувати про помилку в конструкторі - зібрати виняток.

Якщо ваша ініціалізація має обгрунтовані шанси викликати помилку, наприклад, вона передбачає введення вводу-виводу або його параметри можуть бути введеннями користувача, тоді єдиний механізм, який вам відкрито, - це створити виняток. У деяких випадках це може бути не те, що ви хочете, і може бути більше сенсу відокремлювати код схильності до помилок на окрему функцію ініціалізації.

Напевно, найпоширеніший приклад цього - в C ++, якщо проект / організаційний стандарт - це вимкнення виключень.

Державна машина

Це той випадок, коли ви моделюєте об'єкт, який має явні переходи стану. Наприклад, файл або сокет, які можна відкрити і закрити.

У цьому випадку для побудови об'єкта (і його видалення) прийнято мати справу лише з орієнтованими на пам'ять атрибутами (ім'я файлу, порт тощо). Тоді з'являться функції спеціально керувати переходами стану, наприклад, відкриті, закриті, які ефективно ініціалізують і руйнують функції.

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

Недоліком, як говорили інші, є те, що тепер ви покладаєте тягар управління державою на користувача своїх класів. Якщо ви могли б керувати лише побудовою, тоді, скажімо, використовуйте RAII, щоб зробити це автоматично.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.