Насправді це досить просто:
Замість того, щоб мати конструктор, який виконує налаштування,
// c-family pseudo-code
public class Thing {
public Thing (a, b, c, d) { this.x = a; this.y = b; /* ... */ }
}
... дозвольте своєму конструктору зробити мало або взагалі нічого, і напишіть метод, який називається .init
або .initialize
, який би робив те, що зазвичай робить ваш конструктор.
public class Thing {
public Thing () {}
public void initialize (a, b, c, d) {
this.x = a; /*...*/
}
}
Тож тепер замість того, щоб просто так:
Thing thing = new Thing(1, 2, 3, 4);
Ви можете піти:
Thing thing = new Thing();
thing.doSomething();
thing.bind_events(evt_1, evt_2);
thing.initialize(1, 2, 3, 4);
Перевага полягає в тому, що тепер ви можете легше використовувати залежність-впорскування / інверсію контролю у своїх системах.
Замість того, щоб сказати
public class Soldier {
private Weapon weapon;
public Soldier (name, x, y) {
this.weapon = new Weapon();
}
}
Ви можете побудувати солдата, надати йому спосіб оснащення, де ви подаєте йому зброю, а потім викликаєте всі інші функції конструктора.
Тож замість ворогів підкласифікації, коли один солдат має пістолет, а інший - гвинтівку, а інший - рушницю, і це єдина різниця, ви можете просто сказати:
Soldier soldier1 = new Soldier(),
soldier2 = new Soldier(),
soldier3 = new Soldier();
soldier1.equip(new Pistol());
soldier2.equip(new Rifle());
soldier3.equip(new Shotgun());
soldier1.initialize("Bob", 32, 48);
soldier2.initialize("Doug", 57, 200);
soldier3.initialize("Mike", 92, 30);
Те ж саме стосується знищення. Якщо у вас є особливі потреби (видалення слухачів подій, видалення екземплярів з масивів / будь-яких структур, з якими ви працюєте тощо), ви б вручну викликали їх, щоб ви точно знали, коли і де в програмі, що відбувається.
EDIT
Як підкреслив Кріотан, нижче це відповідає на оригінальний пост "Як" , але насправді не робить гарної роботи "Чому".
Як ви, напевно, бачите у відповіді вище, різниці між:
var myObj = new Object();
myObj.setPrecondition(1);
myObj.setOtherPrecondition(2);
myObj.init();
і письмовій формі
var myObj = new Object(1,2);
при цьому просто функція конструктора більша.
Необхідно зробити аргумент для об'єктів, які мають 15 або 20 попередніх умов, що зробить конструктор дуже, дуже важким для роботи, і це полегшить бачення та запам'ятовування, витягнувши ці речі в інтерфейс , так що ви можете бачити, як працює копія на один рівень вище.
Необов'язкова конфігурація об'єктів - це природне продовження цього; необов'язково встановлення значень в інтерфейсі перед тим, як зробити об’єкт запуском.
У JS є кілька чудових ярликів для цієї ідеї, які просто здаються невідповідними в більш сильно набраних мовах c-подібних мов.
Це означає, що, якщо ви маєте справу зі списком аргументів, який тривалий час у вашому конструкторі, шанси на те, що ваш об’єкт занадто великий і робить занадто багато, як є. Знову ж таки, це особисті переваги, і є винятки далеко і всьому, але якщо ви передаєте 20 предметів в об’єкт, великі шанси, що ви зможете знайти спосіб змусити цей об’єкт зробити менше, зробивши менші об'єкти .
Більш доцільною причиною і широко застосовною є те, що ініціалізація об'єкта покладається на асинхронні дані, яких у вас зараз немає.
Ви знаєте, що вам потрібен об’єкт, тому ви все одно збираєтесь його створити, але для того, щоб він функціонував належним чином, йому потрібні дані з сервера або з іншого файлу, який він тепер повинен завантажити.
Знову ж таки, чи передаєте ви потрібні дані в гігантський ініт або ви розробляєте інтерфейс не дуже важливий для концепції, наскільки це важливо для інтерфейсу вашого об'єкта та дизайну вашої системи ...
Але з точки зору побудови об’єкта, ви можете зробити щось подібне:
var obj_w_async_dependencies = new Object();
async_loader.load(obj_w_async_dependencies.async_data, obj_w_async_dependencies);
async_loader
може бути передано ім’я файлу, ім’я ресурсу чи будь-що інше, завантажте цей ресурс - можливо, він завантажує звукові файли чи дані зображення, а може бути, він завантажує збережену статистику символів ...
... і тоді вони повернуть ці дані назад obj_w_async_dependencies.init(result);
.
Така динаміка часто зустрічається у веб-додатках.
Не обов'язково в конструкції об'єкта для додатків вищого рівня: наприклад, галереї можуть завантажуватись та ініціалізуватись відразу, а потім відображати фотографії під час потоку - це насправді не ініціалізація асинхронізації, але там, де вона частіше спостерігається, в бібліотеках JavaScript.
Один модуль може залежати від іншого, і тому ініціалізація цього модуля може бути відкладена до завершення завантаження залежних.
З точки зору ігрових примірників цього, розглянемо фактичний Game
клас.
Чому ми не можемо зателефонувати .start
або .run
в конструктор?
Ресурси потрібно завантажувати - решта всього вже визначена, і це добре, але якщо ми спробуємо запустити гру без підключення до бази даних, або без текстур, моделей, звуків чи рівнів, це не буде особливо цікава гра ...
... тож у чому полягає різниця між тим, що ми бачимо типового Game
, за винятком того, що ми даємо його методу "вперед" ім'я, яке цікавіше, ніж .init
(або, навпаки, ще більше розбиваємо ініціалізацію, відокремлюючи завантаження, налаштування завантажених речей та запуск програми, коли все налаштовано).