poo - herencia en java ejemplos
¿Algún buen ejemplo de heredar de una clase concreta? (18)
Fondo:
Como programador de Java, heredo ampliamente (en lugar de implementar) de las interfaces y, a veces, diseño clases base abstractas. Sin embargo, nunca sentí realmente la necesidad de subclasificar una clase concreta (no abstracta) (en los casos en que lo hice, luego resultó que otra solución, como la delegation , hubiera sido mejor).
Así que ahora estoy empezando a sentir que casi no hay ninguna situación en la que sea apropiado heredar de una clase concreta. Por un lado, el principio de sustitución de Liskov (LSP) parece casi imposible de satisfacer para las clases no triviales; también many otras questions aquí parecen hacerse eco de una opinión similar.
Entonces mi pregunta:
¿En qué situación (si hay alguna) tiene sentido heredar de una clase concreta? ¿Puedes dar un ejemplo concreto y real de una clase que hereda de otra clase concreta, donde sientes que este es el mejor diseño dadas las limitaciones? Me interesarían especialmente los ejemplos que satisfacen el LSP (o ejemplos en los que la satisfacción del LSP parece no tener importancia).
Principalmente tengo conocimientos de Java, pero estoy interesado en ejemplos de cualquier idioma.
Empiezo a sentir que casi no hay situaciones en las que sea apropiado heredar de una clase concreta.
Este es uno ''casi''. Intente escribir un applet sin extender Applet
o JApplet
.
Aquí hay un ejemplo de la información del applet. página .
/* <!-- Defines the applet element used by the appletviewer. -->
<applet code=''HelloWorld'' width=''200'' height=''100''></applet> */
import javax.swing.*;
/** An ''Hello World'' Swing based applet.
To compile and launch:
prompt> javac HelloWorld.java
prompt> appletviewer HelloWorld.java */
public class HelloWorld extends JApplet {
public void init() {
// Swing operations need to be performed on the EDT.
// The Runnable/invokeLater() ensures that happens.
Runnable r = new Runnable() {
public void run() {
// the crux of this simple applet
getContentPane().add( new JLabel("Hello World!") );
}
};
SwingUtilities.invokeLater(r);
}
}
Principalmente tengo conocimientos de Java, pero estoy interesado en ejemplos de cualquier idioma.
Al igual que muchos marcos, ASP.NET hace un uso intensivo de la herencia para compartir el comportamiento entre las clases. Por ejemplo, HtmlInputPassword tiene esta jerarquía de herencia:
System.Object
System.Web.UI.Control
System.Web.UI.HtmlControls.HtmlControl // abstract
System.Web.UI.HtmlControls.HtmlInputControl // abstract
System.Web.UI.HtmlControls.HtmlInputText
System.Web.UI.HtmlControls.HtmlInputPassword
en el cual se pueden ver ejemplos de clases concretas derivadas de.
Si está construyendo un marco de trabajo, y está seguro de que desea hacerlo, es posible que desee tener una gran jerarquía de herencia.
Heredar clases concretas es solo una opción si desea ampliar la funcionalidad de la biblioteca lateral.
Por ejemplo, del uso de la vida real, puede observar la jerarquía de DataInputStream, que implementa la interfaz DataInput para FilterInputStream.
Creo que el siguiente es un buen ejemplo cuando puede ser apropiado:
public class LinkedHashMap<K,V>
extends HashMap<K,V>
Otro buen ejemplo es la herencia de excepciones:
public class IllegalFormatPrecisionException extends IllegalFormatException
public class IllegalFormatException extends IllegalArgumentException
public class IllegalArgumentException extends RuntimeException
public class RuntimeException extends Exception
public class Exception extends Throwable
El decoration , una forma útil de agregar un comportamiento adicional a una clase sin hacerlo demasiado general, hace un uso intensivo de la herencia de las clases concretas. Ya se mencionó aquí, pero bajo un nombre científico de "forwarding wrapper class".
En general, es mejor heredar de una clase abstracta que de una clase concreta. Una clase concreta debe proporcionar una definición para su representación de datos, y algunas subclases necesitarán una representación diferente. Como una clase abstracta no tiene que proporcionar una representación de datos, las subclases futuras pueden usar cualquier representación sin temor a entrar en conflicto con la que heredaron. Incluso nunca encontré una situación en la que sintiera que la herencia concreta es necesaria. Pero podría haber algunas situaciones para la herencia concreta, especialmente cuando se proporciona compatibilidad con versiones anteriores de su software. En ese caso, es posible que haya especializado una class A
pero desea que sea concreta, ya que su aplicación anterior podría estar usándola.
En términos generales, el único momento en que provengo de clases concretas es cuando están en el marco. Derivado de Applet
o JApplet
es el ejemplo trivial.
Encuentro las clases de colección java como un muy buen ejemplo. Así que tienes una colección abstracta con elementos como AbstractList, AbstractSet, AbstractQueue ... Creo que esta jerarquía ha sido bien diseñada ... y para garantizar que no haya explosión está la clase Collections con todas sus clases estáticas internas.
Este es un ejemplo de una implementación actual que estoy emprendiendo.
En el entorno de OAuth 2, dado que la documentación aún está en etapa de borrador, la especificación sigue cambiando (en el momento de escribir esto, estamos en la versión 21).
Por lo tanto, tuve que extender mi clase concreta AccessToken
para acomodar los diferentes tokens de acceso.
En el borrador anterior, no había un token_type
campos token_type
, por lo que el token de acceso real es el siguiente:
public class AccessToken extends OAuthToken {
/**
*
*/
private static final long serialVersionUID = -4419729971477912556L;
private String accessToken;
private String refreshToken;
private Map<String, String> additionalParameters;
//Getters and setters are here
}
Ahora, con los tokens de acceso que devuelven token_type
, tengo
public class TokenTypedAccessToken extends AccessToken {
private String tokenType;
//Getter and setter are here...
}
Entonces, puedo devolver ambos y el usuario final no es el más sabio. :-)
En resumen: si desea una clase personalizada que tenga la misma funcionalidad de su clase concreta sin cambiar la estructura de la clase concreta, le sugiero que amplíe la clase concreta.
Lo haces, por ejemplo, en bibliotecas de GUI. No tiene mucho sentido heredar de un mero Componente y delegar en un Panel. Es probable que muchos facilitadores hereden del Panel directamente.
Muchas respuestas, pero pensé en agregar mi propio $ 0.02.
Anulo las clases de presentación con poca frecuencia, pero bajo algunas circunstancias específicas. Ya se mencionó al menos 1 cuando las clases de framework están diseñadas para ser extendidas. Dos más vienen a la mente con algunos ejemplos:
1) Si quiero ajustar el comportamiento de una clase concreta. A veces quiero cambiar cómo funciona la clase concreta o quiero saber cuándo se llama un determinado método para poder activar algo. A menudo, las clases concretas definirán un método de enlace cuyo único uso es que las subclases anulen el método.
Ejemplo: MBeanExporter
porque necesitamos poder anular el registro de un bean JMX:
public class MBeanRegistrationSupport {
// the concrete class has a hook defined
protected void onRegister(ObjectName objectName) {
}
Nuestra clase:
public class UnregisterableMBeanExporter extends MBeanExporter {
@Override
protected void onUnregister(ObjectName name) {
// always a good idea
super.onRegister(name);
objectMap.remove(name);
}
Aquí hay otro buen ejemplo. LinkedHashMap
está diseñado para tener su método removeEldestEntry
anulado.
private static class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
@Override
protected boolean removeEldestEntry(Entry<K, V> eldest) {
return size() > 1000;
}
2) Si una clase comparte una cantidad significativa de superposición con la clase concreta, excepto por algunos ajustes a la funcionalidad.
Ejemplo: Mi proyecto ORMLite maneja campos persistentes de objetos long
y campos primitivos long
. Ambos tienen casi la misma definición. LongObjectType
proporciona todos los métodos que describen cómo la base de datos trata con campos long
.
public class LongObjectType {
// a whole bunch of methods
mientras que LongType
anula LongObjectType
y solo modifica un único método para decir que maneja primitivas.
public class LongType extends LongObjectType {
...
@Override
public boolean isPrimitive() {
return true;
}
}
Espero que esto ayude.
Otro buen ejemplo serían los tipos de almacenamiento de datos. Para dar un ejemplo preciso: un árbol rojo-negro es un árbol binario más específico, pero la recuperación de datos y otra información como el tamaño se puede manejar de manera idéntica. Por supuesto, una buena biblioteca debería tener eso ya implementado, pero a veces tiene que agregar tipos de datos específicos para su problema.
Actualmente estoy desarrollando una aplicación que calcula matrices para los usuarios. El usuario puede proporcionar configuraciones para influir en el cálculo. Hay varios tipos de matrices que se pueden calcular, pero hay una clara similitud, especialmente en la capacidad de configuración: la matriz A puede usar todas las configuraciones de la matriz B pero tiene parámetros adicionales que se pueden usar. En ese caso, he heredado de ConfigObjectB para mi ConfigObjectA y funciona bastante bien.
Otro caso de uso sería el de anular el comportamiento predeterminado:
Digamos que hay una clase que usa el analizador Jaxb estándar para el análisis
public class Util{
public void mainOperaiton(){..}
protected MyDataStructure parse(){
//standard Jaxb code
}
}
Ahora digo que quiero usar un enlace diferente (Say XMLBean) para la operación de análisis sintáctico,
public class MyUtil extends Util{
protected MyDataStructure parse(){
//XmlBean code code
}
}
Ahora puedo usar el nuevo enlace con la reutilización de código de superclase.
Solo un pensamiento general. A las clases abstractas les falta algo. Tiene sentido si esto, lo que falta, es diferente en cada clase derivada. Pero es posible que tenga un caso en el que no desee modificar una clase, sino simplemente agregar algo. Para evitar la duplicación del código, heredarías. Y si necesita ambas clases, sería herencia de una clase concreta.
Entonces mi respuesta sería: en todos los casos en los que realmente solo quieres agregar algo. Quizás esto simplemente no sucede muy a menudo.
Sus preocupaciones también tienen eco en el principio clásico " favorecer la composición sobre la herencia ", por las razones que usted indicó. No puedo recordar la última vez que heredé de una clase concreta. Cualquier código común que necesite ser reutilizado por clases secundarias casi siempre necesita declarar interfaces abstractas para esas clases. En este orden, trato de preferir las siguientes estrategias:
- Composición (sin herencia)
- Interfaz
- Clase abstracta
Heredar de una clase concreta realmente no es una buena idea.
[EDITAR] Calificaré esta afirmación diciendo que no veo un buen caso de uso cuando se tiene control sobre la arquitectura. Por supuesto, cuando se utiliza una API que lo espera, ¿qué va a hacer? Pero no entiendo las elecciones de diseño hechas por esas API. La clase de llamada siempre debe ser capaz de declarar y usar una abstracción de acuerdo con el Principio de Inversión de Dependencia . Si una clase secundaria tiene interfaces adicionales para ser consumidas, o bien tendrías que violar DIP o hacer un feo casting para acceder a esas interfaces.
Un caso muy común en el que puedo pensar es derivar de los controles básicos de IU, como formularios, cuadros de texto, cuadros combinados, etc. Son completos, concretos y bien capaces de pararse por sí solos; sin embargo, la mayoría de ellos también son muy básicos y, a veces, su comportamiento predeterminado no es el que usted desea. Prácticamente nadie, por ejemplo, usaría una instancia de un Formulario no adulterado, a menos que posiblemente estuvieran creando una capa UI completamente dinámica.
Por ejemplo, en una pieza de software que escribí acerca de la madurez relativa alcanzada recientemente (lo que significa que me quedé sin tiempo para centrarme principalmente en desarrollarla :)), descubrí que necesitaba agregar capacidad de "carga lenta" a ComboBoxes, por lo que no lo haría. t tarda 50 años (en años de computación) para cargar la primera ventana. También necesitaba la capacidad de filtrar automáticamente las opciones disponibles en un ComboBox en función de lo que se mostraba en otro, y finalmente necesitaba una forma de "reflejar" el valor de un ComboBox en otro control editable, y hacer que un cambio en un control ocurra en el otro también Por lo tanto, amplié el ComboBox básico para darle estas características adicionales, y creé dos nuevos tipos: LazyComboBox, y luego, MirroringComboBox. Ambos se basan en el control ComboBox concreto y totalmente útil, simplemente anulando algunos comportamientos y agregando un par de otros. No están muy sueltos y, por lo tanto, no son SOLIDOS, pero la funcionalidad adicional es lo suficientemente genérica como para que, si tuviera que hacerlo, pudiera reescribir cualquiera de estas clases desde cero para hacer el mismo trabajo, posiblemente mejor.
del proyecto gdata :
com.google.gdata.client.Service está diseñado para actuar como una clase base que se puede personalizar para tipos específicos de servicios GData.
Servicio javadoc:
La clase de servicio representa una conexión de cliente a un servicio GData. Encapsula todas las interacciones de nivel de protocolo con el servidor GData y actúa como una clase auxiliar para entidades de nivel superior (fuentes, entradas, etc.) que invocan operaciones en el servidor y procesan sus resultados.
Esta clase proporciona la funcionalidad común de nivel básico requerida para acceder a cualquier servicio GData. También está diseñado para actuar como una clase base que se puede personalizar para tipos específicos de servicios GData. Algunos ejemplos de personalizaciones compatibles son:
Autenticación : implementación de un mecanismo de autenticación personalizado para servicios que requieren autenticación y uso de algo que no sea la autenticación HTTP básica o digestiva.
Extensiones : defina las extensiones esperadas para el feed, la entrada y otros tipos asociados con un servicio.
Formatos : defina representaciones de recursos personalizados adicionales que el servicio y los analizadores y generadores del lado del cliente puedan consumir o producir para manejarlos.
A menudo tiene implementaciones esqueléticas para una interfaz I
Si puede ofrecer extensibilidad sin métodos abstractos (por ejemplo, a través de ganchos), es preferible tener una clase esquemática no abstracta porque puede crear una instancia.
Un ejemplo sería una clase de envoltura de reenvío , para poder reenviar a otro objeto de una clase C
concreta que implementa I
, por ejemplo, habilitar la decoration o la reutilización simple de código de C
sin tener que heredar de C
Puede encontrar un ejemplo así en el elemento 16 de Java efectivo , favorecer la composición sobre la herencia. (No quiero publicarlo aquí debido a los derechos de autor, pero en realidad es simplemente reenviar todas las llamadas de método de I
a la implementación envuelta).