Дуже багато людей відповіли. Думав, я даю власну особисту перспективу.
Колись я працював над додатком (і досі займаюся), який створює музику.
Додаток було абстрактний Scale
клас з декількома підкласів: CMajor
, DMinor
і т.д. Scale
дивився що - щось на зразок цього:
public abstract class Scale {
protected Note[] notes;
public Scale() {
loadNotes();
}
// .. some other stuff ommited
protected abstract void loadNotes(); /* subclasses put notes in the array
in this method. */
}
Музичні генератори працювали з певним Scale
екземпляром для створення музики. Користувач вибрав би шкалу зі списку, щоб створювати музику.
Одного разу на думку мені прийшла класна ідея: чому б не дозволити користувачеві створювати власні ваги? Користувач буде вибирати нотатки зі списку, натискати кнопку, і нова масштабність буде додана до доступних масштабів списку.
Але я не зміг цього зробити. Це було тому, що всі шкали вже встановлені під час компіляції - оскільки вони виражаються як класи. Потім мене вдарило:
Дуже часто інтуїтивно мислити термінами «суперкласи та підкласи». Практично все можна висловити за допомогою цієї системи: суперклас Person
та підкласи John
та Mary
; суперклас Car
та підкласи Volvo
та Mazda
; суперклас Missile
і підкласи SpeedRocked
, LandMine
іTrippleExplodingThingy
.
Дуже природно мислити таким чином, особливо для людини відносно нової в ОО.
Але ми завжди повинні пам’ятати, що класи - це шаблони , а об’єкти вміщуються в ці шаблони . Ви можете залити потрібний вміст шаблону, створюючи незліченну кількість можливостей.
Це не підклас, щоб заповнити шаблон. Це завдання об’єкта. Завдання підкласу - додати фактичну функціональність або розширити шаблон .
І тому я повинен створити конкретний Scale
клас із Note[]
полем, і дозволити об'єктам заповнити цей шаблон ; можливо, через конструктор чи щось таке. І врешті-решт, так я і зробив.
Кожен раз, коли ви проектуєте шаблон у класі (наприклад, порожній Note[]
член, який потрібно заповнити, або String name
поле, якому потрібно призначити значення), пам’ятайте, що завдання об’єктів цього класу заповнювати шаблон ( або, можливо, тих, хто створює ці об’єкти). Підкласи призначені для додавання функціональності, а не для заповнення шаблонів.
Можливо, ви б спокусилися створити "суперклас Person
, підкласи John
та Mary
" таку систему, як ви робили, тому що вам подобається формальність, яку ви отримуєте.
Таким чином, ви можете просто сказати Person p = new Mary()
, а не Person p = new Person("Mary", 57, Sex.FEMALE)
. Це робить речі більш організованими та більш структурованими. Але, як ми вже говорили, створення нового класу для кожної комбінації даних - це не дуже вдалий підхід, оскільки він зникає з коду ні за що і обмежує вас з точки зору можливостей виконання.
Тож ось рішення: користуйтеся базовою фабрикою, можливо навіть статичною. Приблизно так:
public final class PersonFactory {
private PersonFactory() { }
public static Person createJohn(){
return new Person("John", 40, Sex.MALE);
}
public static Person createMary(){
return new Person("Mary", 57, Sex.FEMALE);
}
// ...
}
Таким чином, ви можете легко використовувати "пресети" "прийти з програмою", наприклад: Person mary = PersonFactory.createMary()
але ви також залишаєте за собою право динамічно проектувати нових людей, наприклад, якщо ви хочете дозволити користувачеві робити це . Наприклад:
// .. requesting the user for input ..
String name = // user input
int age = // user input
Sex sex = // user input, interpreted
Person newPerson = new Person(name, age, sex);
Або ще краще: зробіть щось подібне:
public final class PersonFactory {
private PersonFactory() { }
private static Map<String, Person> persons = new HashMap<>();
private static Map<String, PersonData> personBlueprints = new HashMap<>();
public static void addPerson(Person person){
persons.put(person.getName(), person);
}
public static Person getPerson(String name){
return persons.get(name);
}
public static Person createPerson(String blueprintName){
PersonData data = personBlueprints.get(blueprintName);
return new Person(data.name, data.age, data.sex);
}
// .. or, alternative to the last method
public static Person createPerson(String personName){
Person blueprint = persons.get(personName);
return new Person(blueprint.getName(), blueprint.getAge(), blueprint.getSex());
}
}
public class PersonData {
public String name;
public int age;
public Sex sex;
public PersonData(String name, int age, Sex sex){
this.name = name;
this.age = age;
this.sex = sex;
}
}
Я захопився. Я думаю, ви зрозуміли ідею.
Підкласи не призначені для заповнення шаблонів, встановлених їх суперкласами. Підкласи призначені для додавання функціональності . Об'єкт призначений для заповнення шаблонів, саме для цього вони і призначені.
Ви не повинні створювати новий клас для кожної можливої комбінації даних. (Як і я не повинен був створювати новий Scale
підклас для кожної можливої комбінації Note
s).
Її керівництво: щоразу, коли ви створюєте новий підклас, враховуйте, чи додає він будь-якого нового функціоналу, який не існує в надкласі. Якщо відповідь на це запитання - «ні», ви, можливо, намагаєтесь «заповнити шаблон» надкласу, і в цьому випадку просто створіть об’єкт. (І, можливо, Фабрика з «пресетами», щоб полегшити життя).
Сподіваюся, що це допомагає.