Mucha gente ya respondió. Pensé que daría mi propia perspectiva personal.
Érase una vez que trabajé en una aplicación (y todavía lo hago) que crea música.
La aplicación tuvo un resumen Scale
clase con varias subclases: CMajor
, DMinor
, etc. Scale
parecía algo así:
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. */
}
Los generadores de música trabajaron con una Scale
instancia específica para generar música. El usuario seleccionaría una escala de una lista para generar música.
Un día, se me ocurrió una idea genial: ¿por qué no permitir que el usuario cree sus propias escalas? El usuario seleccionaría notas de una lista, presionaría un botón y se agregaría una nueva escala a la lista de escalas disponibles.
Pero no pude hacer esto. Esto se debió a que todas las escalas ya están configuradas en tiempo de compilación, ya que se expresan como clases. Entonces me golpeó:
A menudo es intuitivo pensar en términos de 'superclases y subclases'. Casi todo se puede expresar a través de este sistema: superclase Person
y subclases John
y Mary
; superclase Car
y subclases Volvo
y Mazda
; superclase Missile
y subclases SpeedRocked
, LandMine
yTrippleExplodingThingy
.
Es muy natural pensar de esta manera, especialmente para la persona relativamente nueva en OO.
Pero siempre debemos recordar que las clases son plantillas , y los objetos son contenido vertido en estas plantillas . Puede verter el contenido que desee en la plantilla, creando innumerables posibilidades.
No es el trabajo de la subclase llenar la plantilla. Es el trabajo del objeto. El trabajo de la subclase es agregar funcionalidad real o expandir la plantilla .
Y es por eso que debería haber creado una Scale
clase concreta , con un Note[]
campo, y dejar que los objetos completen esta plantilla ; posiblemente a través del constructor o algo así. Y eventualmente, así lo hice.
Cada vez que diseñe una plantilla en una clase (p. Ej., Un Note[]
miembro vacío que debe llenarse o un String name
campo al que se le debe asignar un valor), recuerde que es el trabajo de los objetos de esta clase completar la plantilla ( o posiblemente aquellos que crean estos objetos). Las subclases están destinadas a agregar funcionalidad, no a completar plantillas.
Es posible que sienta la tentación de crear un tipo de sistema de "superclase Person
, subclases John
y Mary
", como lo hizo, porque le gusta la formalidad que esto le da.
De esta manera, solo puedes decir Person p = new Mary()
, en lugar de Person p = new Person("Mary", 57, Sex.FEMALE)
. Hace las cosas más organizadas y más estructuradas. Pero como dijimos, crear una nueva clase para cada combinación de datos no es un buen enfoque, ya que hincha el código para nada y lo limita en términos de habilidades de tiempo de ejecución.
Así que aquí hay una solución: usar una fábrica básica, incluso podría ser estática. Al igual que:
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 esta manera, puede usar fácilmente los 'ajustes preestablecidos' y el 'viene con el programa', así: Person mary = PersonFactory.createMary()
pero también se reserva el derecho de diseñar dinámicamente nuevas personas, por ejemplo, en el caso de que desee permitir que el usuario lo haga. . P.ej:
// .. 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 incluso mejor: haz algo así:
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;
}
}
Me dejé llevar. Creo que entiendes la idea.
Las subclases no están destinadas a completar las plantillas establecidas por sus superclases. Las subclases están destinadas a agregar funcionalidad . Los objetos están destinados a completar las plantillas, para eso están.
No debería crear una nueva clase para cada combinación posible de datos. (Al igual que no debería haber creado una nueva Scale
subclase para cada combinación posible de Note
s).
Es una pauta: cada vez que cree una nueva subclase, considere si agrega alguna funcionalidad nueva que no existe en la superclase. Si la respuesta a esa pregunta es "no", entonces podría estar tratando de 'completar la plantilla' de la superclase, en cuyo caso simplemente cree un objeto. (Y posiblemente una Fábrica con 'presets', para hacer la vida más fácil).
Espero que ayude.