Шаблон Builder не вирішує "проблему" багатьох аргументів. Але чому багато аргументів проблематичні?
- Вони вказують, що ваш клас може робити занадто багато . Однак існує багато типів, які легітимно містять багато членів, які неможливо скласти з групою.
- Тестування та розуміння функції з багатьма входами стає експоненціально складніше - буквально!
- Якщо мова не пропонує названих параметрів, виклик функції не є самодокументуванням . Читати виклик функції з багатьма аргументами досить складно, оскільки ви поняття не маєте, що повинен робити 7-й параметр. Ви навіть не помітите, якщо 5-й та 6-й аргументи були замінені випадково, особливо якщо ви є динамічно набраною мовою або все відбувається як рядок, або коли останній параметр
true
з якихось причин.
Підробка названих параметрів
У шаблоні Builder адресу тільки одна з цих проблем, а саме ремонтопридатність проблем викликів функцій з багатьма аргументами * . Отже, виклик функції, як
MyClass o = new MyClass(a, b, c, d, e, f, g);
може стати
MyClass o = MyClass.builder()
.a(a).b(b).c(c).d(d).e(e).f(f).g(g)
.build();
Pattern Шаблон Builder спочатку був задуманий як представницько-агностичний підхід до збирання складених об'єктів, що є набагато більшим прагненням, ніж щойно названі аргументи для параметрів. Зокрема, для конструктора не потрібен вільний інтерфейс.
Це забезпечує трохи додаткової безпеки, оскільки вона вибухне, якщо ви почнете використовувати метод будівельника, який не існує, але в іншому випадку не принесе вам нічого, що коментар у виклику конструктора не мав би. Крім того, для створення конструктора вручну потрібен код, і більше коду завжди може містити більше помилок.
У мовах, де легко визначити новий тип значення, я виявив, що краще використовувати мікротипізацію / крихітні типи для імітації названих аргументів. Він названий так, тому що типи дійсно невеликі, але ви в кінцевому підсумку набираєте набагато більше ;-)
MyClass o = new MyClass(
new MyClass.A(a), new MyClass.B(b), new MyClass.C(c),
new MyClass.D(d), new MyClass.E(e), new MyClass.F(f),
new MyClass.G(g));
Очевидно, що імена типів A
, B
, C
, ... повинні бути іменами самодокументірованни, що ілюструє значення параметра часто тим же ім'я , як ви б дати змінний параметр. У порівнянні з ідіомою конструктора для іменних аргументів необхідна реалізація набагато простіша, і, таким чином, менше ймовірність містити помилки. Наприклад (із синтаксисом Java-ish):
class MyClass {
...
public static class A {
public final int value;
public A(int a) { value = a; }
}
...
}
Компілятор допомагає гарантувати, що всі аргументи були надані; з Builder вам доведеться вручну перевірити відсутність аргументів або кодувати стан машини в системі типу хост-мови - обидва, ймовірно, містять помилки.
Існує ще один загальний підхід до імітації названих аргументів: єдиний абстрактний параметричний об'єкт, який використовує синтаксис вбудованого класу для ініціалізації всіх полів. На Java:
MyClass o = new MyClass(new MyClass.Arguments(){{ argA = a; argB = b; argC = c; ... }});
class MyClass {
...
public static abstract class Arguments {
public int argA;
public String ArgB;
...
}
}
Однак можна забути поля, і це досить специфічне для мови рішення (я бачив використання в JavaScript, C # і C).
На щастя, конструктор все ще може перевірити всі аргументи, що не є випадком, коли ваші об’єкти створені в частково сконструйованому стані, і вимагають від користувача подання додаткових аргументів за допомогою сеттерів або init()
методу - вони вимагають найменших зусиль кодування, але роблять складніше писати правильні програми.
Тож, хоча існує багато підходів до вирішення "багатьох неназваних параметрів, які ускладнюють підтримку коду", залишаються інші проблеми.
Підхід до кореневої проблеми
Наприклад, проблема заповідності. Коли я пишу одиничні тести, мені потрібна можливість вводити тестові дані та забезпечувати виконання тестових реалізацій для висміювання залежностей та операцій, які мають зовнішні побічні ефекти. Я не можу цього зробити, коли ви створюєте інстанціювання будь-яких класів у своєму конструкторі. Якщо відповідальність вашого класу не є створенням інших об'єктів, він не повинен створювати жодних нетривіальних класів. Це іде разом із проблемою єдиної відповідальності. Чим більше зосереджена відповідальність класу, тим легше його тестувати (а часто простіше у використанні).
Найпростіший і найчастіше найкращий підхід - це конструктор приймати повністю побудовані залежності як параметр , хоча це покладає на відповідальність за управління залежностями перед абонентом - також не ідеально, якщо тільки залежності не є незалежними об'єктами у вашій моделі домену.
Іноді замість цього використовуються (абстрактні) фабрики або системи повного введення залежності , хоча вони можуть бути надмірними в більшості випадків використання. Зокрема, вони зменшують кількість аргументів лише у тому випадку, якщо багато з цих аргументів є квазіглобальними об'єктами або значеннями конфігурації, які не змінюються між інстанцією об'єктів. Наприклад , якщо параметри a
і d
були глобальні-Робочі, ми отримаємо
Dependencies deps = new Dependencies(a, d);
...
MyClass o = deps.newMyClass(b, c, e, f, g);
class MyClass {
MyClass(Dependencies deps, B b, C c, E e, F f, G g) {
this.depA = deps.newDepA(b, c);
this.depB = deps.newDepB(e, f);
this.g = g;
}
...
}
class Dependencies {
private A a;
private D d;
public Dependencies(A a, D d) { this.a = a; this.d = d; }
public DepA newDepA(B b, C c) { return new DepA(a, b, c); }
public DepB newDepB(E e, F f) { return new DepB(d, e, f); }
public MyClass newMyClass(B b, C c, E e, F f, G g) {
return new MyClass(deps, b, c, e, f, g);
}
}
Залежно від програми, це може бути зміна ігор, коли фабричні методи в кінцевому підсумку не мають майже ніяких аргументів, оскільки все це може бути надано менеджером залежності, або це може бути велика кількість коду, що ускладнює інстанціювання без видимої вигоди. Такі заводи набагато корисніші для відображення інтерфейсів на конкретні типи, ніж для управління параметрами. Однак цей підхід намагається вирішити основну проблему занадто багатьох параметрів, а не просто приховувати її за допомогою досить вільного інтерфейсу.