Muitas pessoas já responderam. Pensei em dar minha própria perspectiva pessoal.
Era uma vez eu trabalhei em um aplicativo (e ainda faço) que cria música.
O aplicativo teve um resumo Scale
classe com várias subclasses: CMajor
, DMinor
, etc. Scale
parecia algo como isso:
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. */
}
Os geradores de música trabalharam com uma Scale
instância específica para gerar música. O usuário selecionaria uma escala de uma lista para gerar música.
Um dia, uma ideia interessante me veio à mente: por que não permitir que o usuário crie suas próprias escalas? O usuário selecionaria notas de uma lista, pressionaria um botão e uma nova escala seria adicionada à lista de escalas disponíveis.
Mas não fui capaz de fazer isso. Isso ocorreu porque todas as escalas já estão definidas em tempo de compilação - uma vez que são expressas como classes. Então me ocorreu:
Muitas vezes, é intuitivo pensar em termos de 'superclasses e subclasses'. Quase tudo pode ser expresso através deste sistema: superclasse Person
e subclasse John
e Mary
; superclasse Car
e subclasse Volvo
e Mazda
; superclasse Missile
e subclasses SpeedRocked
, LandMine
e TrippleExplodingThingy
.
É muito natural pensar dessa maneira, especialmente para a pessoa relativamente nova no OO.
Mas devemos sempre lembrar que classes são modelos e objetos são conteúdo derramado nesses modelos . Você pode derramar qualquer conteúdo que desejar no modelo, criando inúmeras possibilidades.
Não é tarefa da subclasse preencher o modelo. É o trabalho do objeto. O trabalho da subclasse é adicionar funcionalidade real ou expandir o modelo .
E é por isso que eu deveria ter criado uma Scale
classe concreta , com um Note[]
campo, e deixar objetos preencherem esse modelo ; possivelmente através do construtor ou algo assim. E, eventualmente, eu fiz.
Toda vez que você cria um modelo em uma classe (por exemplo, um Note[]
membro vazio que precisa ser preenchido ou um String name
campo que precisa receber um valor), lembre-se de que é tarefa dos objetos dessa classe preencher o modelo ( ou possivelmente aqueles que criam esses objetos). As subclasses são destinadas a adicionar funcionalidade, não a preencher modelos.
Você pode ser tentado a criar um tipo de sistema de "superclasse Person
, subclasse John
e Mary
", como você, porque gosta da formalidade que isso leva a você.
Dessa forma, você pode apenas dizer Person p = new Mary()
, em vez de Person p = new Person("Mary", 57, Sex.FEMALE)
. Torna as coisas mais organizadas e mais estruturadas. Mas, como dissemos, criar uma nova classe para cada combinação de dados não é uma boa abordagem, pois sobrecarrega o código e limita você em termos de habilidades de tempo de execução.
Então, aqui está uma solução: use uma fábrica básica, pode até ser estática. Igual a:
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);
}
// ...
}
Dessa forma, você pode facilmente usar as 'predefinições' e 'vem com o programa', assim:, Person mary = PersonFactory.createMary()
mas também se reserva o direito de projetar novas pessoas dinamicamente, por exemplo, no caso em que você deseja permitir que o usuário faça isso . Por exemplo:
// .. 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);
Ou melhor ainda: faça algo assim:
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;
}
}
Eu me empolguei. Eu acho que você entendeu a ideia.
As subclasses não devem preencher os modelos definidos por suas superclasses. As subclasses são destinadas a adicionar funcionalidade . Objetos devem preencher os modelos, é para isso que eles servem.
Você não deve criar uma nova classe para todas as combinações possíveis de dados. (Assim como eu não deveria ter criado uma nova Scale
subclasse para todas as combinações possíveis de Note
s).
Aqui está uma diretriz: sempre que você criar uma nova subclasse, considere se ela inclui alguma nova funcionalidade que não exista na superclasse. Se a resposta a essa pergunta for "não", você pode estar tentando 'preencher o modelo' da superclasse, nesse caso, basta criar um objeto. (E possivelmente uma fábrica com 'presets', para facilitar a vida).
Espero que ajude.