El argumento "horrible para la memoria" es completamente erróneo, pero es objetivamente una "mala práctica". Cuando hereda de una clase, no solo hereda los campos y métodos que le interesan. En cambio, hereda todo . Cada método que declara, incluso si no es útil para usted. Y lo más importante, también hereda todos sus contratos y garantías que ofrece la clase.
El acrónimo SOLID proporciona algunas heurísticas para un buen diseño orientado a objetos. Aquí, el que Nterface Segregación Principio (ISP) y la L iskov Sustitución Pricinple (LSP) tiene algo que decir.
El ISP nos dice que mantengamos nuestras interfaces lo más pequeñas posible. Pero al heredar de ArrayList
, obtienes muchos, muchos métodos. ¿Tiene algún sentido get()
, remove()
, set()
(sustituir), o add()
(insertar) un nodo hijo en un índice en particular? ¿Es sensible a ensureCapacity()
la lista subyacente? ¿Qué significa para sort()
un nodo? ¿Realmente se supone que los usuarios de tu clase obtienen un subList()
? Como no puede ocultar los métodos que no desea, la única solución es tener ArrayList como una variable miembro y reenviar todos los métodos que realmente desee:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
Si realmente desea todos los métodos que ve en la documentación, podemos pasar al LSP. El LSP nos dice que debemos poder usar la subclase donde se espera la clase principal. Si una función toma un ArrayList
parámetro como y pasamos nuestro Node
en su lugar, se supone que nada cambiará.
La compatibilidad de las subclases comienza con cosas simples como firmas de tipo. Cuando anula un método, no puede hacer que los tipos de parámetros sean más estrictos, ya que eso podría excluir los usos que eran legales con la clase principal. Pero eso es algo que el compilador nos busca en Java.
Pero el LSP es mucho más profundo: tenemos que mantener la compatibilidad con todo lo que promete la documentación de todas las clases e interfaces principales. En su respuesta , Lynn ha encontrado un caso en el que la List
interfaz (que ha heredado a través de ArrayList
) garantiza cómo se supone que funcionan los métodos equals()
y hashCode()
. Para hashCode()
que se les da incluso un algoritmo particular que debe ser implementada con exactitud. Supongamos que ha escrito esto Node
:
public class Node extends ArrayList<Node> {
public final int value;
public Node(int value, Node... children) {
this.value = Value;
for (Node child : children)
add(child);
}
...
}
Esto requiere que el value
no pueda contribuir al hashCode()
y no pueda influir equals()
. La List
interfaz, que promete honrar heredando de ella, debe new Node(0).equals(new Node(123))
ser verdadera.
Debido a que heredar de las clases hace que sea demasiado fácil romper accidentalmente una promesa que hizo una clase principal, y debido a que generalmente expone más métodos de los que pretendía, generalmente se sugiere que prefiera la composición sobre la herencia . Si debe heredar algo, se sugiere heredar solo las interfaces. Si desea reutilizar el comportamiento de una clase en particular, puede mantenerlo como un objeto separado en una variable de instancia, de esa manera no se le imponen todas sus promesas y requisitos.
A veces, nuestro lenguaje natural sugiere una relación de herencia: un automóvil es un vehículo. Una motocicleta es un vehículo. ¿Debo definir clases Car
y Motorcycle
que hereden de una Vehicle
clase? El diseño orientado a objetos no se trata de reflejar el mundo real exactamente en nuestro código. No podemos codificar fácilmente las ricas taxonomías del mundo real en nuestro código fuente.
Un ejemplo de ello es el problema de modelado empleado-jefe. Tenemos varios correos Person
electrónicos, cada uno con un nombre y una dirección. Un Employee
es un Person
y tiene un Boss
. A Boss
es también a Person
. Entonces, ¿debería crear una Person
clase que sea heredada por Boss
y Employee
? Ahora tengo un problema: el jefe también es un empleado y tiene otro superior. Entonces parece que Boss
debería extenderse Employee
. Pero el CompanyOwner
es un Boss
pero no es un Employee
? Cualquier tipo de gráfico de herencia se descompondrá de alguna manera aquí.
OOP no se trata de jerarquías, herencia y reutilización de clases existentes, se trata de generalizar el comportamiento . OOP se trata de "Tengo un montón de objetos y quiero que hagan algo en particular, y no me importa cómo". Para eso están las interfaces . Si implementa la Iterable
interfaz para usted Node
porque desea que sea iterable, está perfectamente bien. Si implementa la Collection
interfaz porque desea agregar / eliminar nodos secundarios, etc., está bien. Pero heredar de otra clase porque te da todo lo que no es, o al menos no a menos que lo hayas pensado detenidamente como se describe anteriormente.