Многие уже ответили. Думаю, я бы дал свою собственную точку зрения.
Когда-то я работал над приложением (и до сих пор занимаюсь), которое создает музыку.
Приложение было абстрактный 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).
Это руководство: всякий раз, когда вы создаете новый подкласс, подумайте, добавляет ли он какие-либо новые функциональные возможности, которых нет в суперклассе. Если ответ на этот вопрос «нет», то вы, возможно, пытаетесь «заполнить шаблон» суперкласса, и в этом случае просто создайте объект. (И, возможно, Фабрика с «пресетами», чтобы облегчить жизнь).
Надеюсь, это поможет.