Molte persone hanno già risposto. Ho pensato di dare la mia prospettiva personale.
Una volta ho lavorato su un'app (e lo faccio ancora) che crea musica.
L'applicazione ha avuto un abstract Scale
classe con diverse sottoclassi: CMajor
, DMinor
, ecc Scale
sembrava una cosa in questo modo:
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. */
}
I generatori di musica hanno lavorato con Scale
un'istanza specifica per generare musica. L'utente selezionerebbe una scala da un elenco, da cui generare musica.
Un giorno mi è venuta in mente un'idea interessante: perché non consentire all'utente di creare le proprie scale? L'utente seleziona le note da un elenco, preme un pulsante e una nuova scala verrà aggiunta all'elenco delle scale disponibili.
Ma non sono stato in grado di farlo. Questo perché tutte le scale sono già impostate al momento della compilazione, poiché sono espresse come classi. Poi mi ha colpito:
È spesso intuitivo pensare in termini di "superclassi e sottoclassi". Quasi tutto può essere espresso attraverso questo sistema: superclasse Person
e sottoclassi John
e Mary
; superclasse Car
e sottoclassi Volvo
e Mazda
; superclasse Missile
e sottoclassi SpeedRocked
, LandMine
e TrippleExplodingThingy
.
È molto naturale pensare in questo modo, soprattutto per la persona relativamente nuova a OO.
Ma dovremmo sempre ricordare che le classi sono modelli e gli oggetti sono contenuti inseriti in questi modelli . Puoi inserire qualsiasi contenuto nel modello, creando infinite possibilità.
Compilare il modello non è compito della sottoclasse. È il lavoro dell'oggetto. Il lavoro della sottoclasse è aggiungere funzionalità effettive o espandere il modello .
Ed è per questo che avrei dovuto creare una Scale
classe concreta , con un Note[]
campo, e lasciare che gli oggetti riempissero questo modello ; forse attraverso il costruttore o qualcosa del genere. E alla fine, così ho fatto.
Ogni volta che si progetta un modello in una classe (ad esempio, un Note[]
membro vuoto che deve essere riempito o un String name
campo a cui è necessario assegnare un valore), ricordare che è compito degli oggetti di questa classe riempire il modello ( o possibilmente quelli che creano questi oggetti). Le sottoclassi hanno lo scopo di aggiungere funzionalità, non di compilare modelli.
Potresti essere tentato di creare una "superclasse Person
, sottoclasse John
e Mary
" tipo di sistema, come hai fatto tu, perché ti piace la formalità che ti procura.
In questo modo, puoi semplicemente dire Person p = new Mary()
, invece di Person p = new Person("Mary", 57, Sex.FEMALE)
. Rende le cose più organizzate e più strutturate. Ma come abbiamo detto, creare una nuova classe per ogni combinazione di dati non è un buon approccio, poiché gonfia il codice per nulla e ti limita in termini di capacità di runtime.
Quindi ecco una soluzione: utilizzare una fabbrica di base, potrebbe anche essere statica. Così:
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);
}
// ...
}
In questo modo, puoi facilmente usare i "preset" come "vieni con il programma", in questo modo:, Person mary = PersonFactory.createMary()
ma ti riservi anche il diritto di progettare nuove persone in modo dinamico, ad esempio nel caso in cui tu voglia consentire all'utente di farlo . Per esempio:
// .. 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);
O ancora meglio: fai qualcosa del genere:
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;
}
}
Sono stato portato via. Penso che tu abbia avuto l'idea.
Le sottoclassi non intendono compilare i modelli impostati dalle rispettive superclassi. Le sottoclassi hanno lo scopo di aggiungere funzionalità . Gli oggetti hanno lo scopo di compilare i modelli, ecco a cosa servono.
Non dovresti creare una nuova classe per ogni possibile combinazione di dati. (Proprio come non avrei dovuto creare una nuova Scale
sottoclasse per ogni possibile combinazione di Note
s).
È una linea guida: ogni volta che crei una nuova sottoclasse, considera se aggiunge nuove funzionalità che non esistono nella superclasse. Se la risposta a questa domanda è "no", allora potresti provare a "compilare il modello" della superclasse, nel qual caso basta creare un oggetto. (E possibilmente una Fabbrica con "preset", per semplificare la vita).
Spero che sia d'aiuto.