Beaucoup de gens ont déjà répondu. Je pensais donner mon point de vue personnel.
Il était une fois, j'ai travaillé (et je le fais encore) sur une application qui crée de la musique.
L'application avait un résumé Scale
classe avec plusieurs sous - classes: CMajor
, DMinor
, etc. Scale
avait l' air quelque chose comme ceci:
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. */
}
Les générateurs de musique ont travaillé avec une Scale
instance spécifique pour générer de la musique. L'utilisateur sélectionnerait une balance dans une liste pour générer de la musique.
Un jour, une idée géniale m'est venue à l'esprit: pourquoi ne pas permettre à l'utilisateur de créer ses propres balances? L'utilisateur sélectionnerait des notes dans une liste, appuierait sur un bouton et une nouvelle échelle serait ajoutée à la liste des échelles disponibles.
Mais je n'ai pas pu faire ça. En effet, toutes les échelles sont déjà définies au moment de la compilation, car elles sont exprimées sous forme de classes. Puis ça m'a frappé:
Il est souvent intuitif de penser en termes de «superclasses et sous-classes». Presque tout peut être exprimé à travers ce système: superclasse Person
et sous John
- classes et Mary
; superclasse Car
et sous Volvo
- classes et Mazda
; superclasse Missile
et sous SpeedRocked
- classes , LandMine
et TrippleExplodingThingy
.
C'est très naturel de penser de cette façon, surtout pour la personne relativement nouvelle en OO.
Mais nous devrions toujours nous rappeler que les classes sont des modèles et que les objets sont du contenu versé dans ces modèles . Vous pouvez verser le contenu que vous voulez dans le modèle, créant ainsi d'innombrables possibilités.
Ce n'est pas le travail de la sous-classe de remplir le modèle. C'est le travail de l'objet. Le travail de la sous-classe consiste à ajouter une fonctionnalité réelle ou à développer le modèle .
Et c’est pourquoi j’aurais dû créer une Scale
classe concrète , avec un Note[]
champ, et laisser les objets remplir ce modèle ; éventuellement par le constructeur ou quelque chose. Et finalement, alors j'ai fait.
Chaque fois que vous concevez un modèle dans une classe (par exemple, un Note[]
membre vide qui doit être rempli ou un String name
champ auquel une valeur doit être affectée), rappelez-vous qu'il appartient aux objets de cette classe de remplir le modèle ( ou éventuellement ceux qui créent ces objets). Les sous-classes sont conçues pour ajouter des fonctionnalités et non pour remplir des modèles.
Vous pourriez être tenté de créer une "super classe Person
, des sous John
- classes et Mary
" un système, comme vous l'avez fait, car vous aimez la formalité que cela vous procure.
De cette façon, vous pouvez simplement dire Person p = new Mary()
, au lieu de Person p = new Person("Mary", 57, Sex.FEMALE)
. Cela rend les choses plus organisées et plus structurées. Mais comme nous l’avons dit, créer une nouvelle classe pour chaque combinaison de données n’est pas une bonne approche, car cela alourdit le code pour rien et vous limite en termes de capacités d’exécution.
Voici donc une solution: utiliser une usine de base, voire statique. Ainsi:
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);
}
// ...
}
De cette façon, vous pouvez facilement utiliser les "préréglages" du "venir avec le programme", comme suit:, Person mary = PersonFactory.createMary()
mais vous vous réservez également le droit de concevoir de nouvelles personnes de manière dynamique, par exemple dans le cas où vous souhaitez autoriser l'utilisateur à le faire. . Par exemple:
// .. 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 même mieux: faites quelque chose comme ça:
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;
}
}
Je me suis emporté. Je pense que vous avez l'idée.
Les sous-classes ne sont pas destinées à renseigner les modèles définis par leurs super-classes. Les sous-classes sont destinées à ajouter des fonctionnalités . Les objets sont destinés à remplir les modèles, c'est à quoi ils servent.
Vous ne devriez pas créer une nouvelle classe pour toutes les combinaisons possibles de données. (Tout comme je n'aurais pas dû créer une nouvelle Scale
sous-classe pour toutes les combinaisons possibles de Note
s).
Her'es une directive: chaque fois que vous créez une nouvelle sous-classe, demandez-vous si elle ajoute une nouvelle fonctionnalité qui n'existe pas dans la super-classe. Si la réponse à cette question est "non", vous pourriez peut-être essayer de "remplir le modèle" de la superclasse, auquel cas il suffit de créer un objet. (Et éventuellement une Factory avec des 'presets', pour rendre la vie plus facile).
J'espère que ça t'as aidé.