Molte persone hanno già risposto. Ho pensato di dare la mia prospettiva personale.
C'era una volta che ho lavorato su un'app (e ancora faccio) che crea musica.
L'app ha una classe Scale
astratta con diverse sottoclassi: CMajor
, DMinor
, ecc. Scale
ha un aspetto simile a questo:
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 un'istanza Scale
specifica per generare musica. L'utente selezionerebbe una scala da un elenco, per generare musica da.
Un giorno mi è venuta 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 ero in grado di farlo. Questo perché tutte le scale sono già impostate in fase di 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 sottoclasse SpeedRocked
, LandMine
e TrippleExplodingThingy
.
È molto naturale pensare in questo modo, specialmente per la persona relativamente nuova a OO.
Ma dovremmo sempre ricordare che classi sono modelli e oggetti sono contenuti riversati in questi modelli . Puoi riversare qualsiasi contenuto nel modello, creando innumerevoli possibilità.
Non è compito della sottoclasse riempire il modello. È il lavoro dell'oggetto. Il compito della sottoclasse è di aggiungere funzionalità effettiva o espandere il modello .
Ed è per questo che avrei dovuto creare una concreta classe Scale
, con un campo Note[]
, e lasciare che gli oggetti riempiano questo modello ; possibilmente attraverso il costruttore o qualcosa del genere. E alla fine, così ho fatto.
Ogni volta che progetti un modello in una classe (ad esempio, un membro Note[]
vuoto che deve essere riempito, o un campo String name
a cui è necessario assegnare un valore), ricorda 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 un tipo di sistema "superclasse Person
, sottoclassi John
e Mary
", come hai fatto tu, perché ti piace la formalità che questo ti consente.
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, dal momento che fa gonfiare 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. Mi piace 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 utilizzare i 'preset' il 'come con il programma', in questo modo: Person mary = PersonFactory.createMary()
, ma ti riservi anche il diritto di progettare nuove persone dinamicamente, 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 sono pensate per riempire i modelli impostati dalle loro superclassi. Le sottoclassi hanno lo scopo di aggiungere funzionalità . Oggetto è pensato per riempire 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 sottoclasse Scale
per ogni possibile combinazione di Note
s).
Ecco una linea guida: ogni volta che crei una nuova sottoclasse, valuta se aggiunge nuove funzionalità che non esistono nella superclasse. Se la risposta a questa domanda è "no", potresti provare a "riempire il modello" della superclasse, nel qual caso crea un oggetto. (e possibilmente una fabbrica con "preset" ', per rendere la vita più facile).
Spero che ti aiuti.