¿Es cierto que anular métodos concretos es un olor a código? Porque creo que si necesita anular métodos concretos:
public class A{
public void a(){
}
}
public class B extends A{
@Override
public void a(){
}
}
se puede reescribir como
public interface A{
public void a();
}
public class ConcreteA implements A{
public void a();
}
public class B implements A{
public void a(){
}
}
y si B quiere reutilizar a () en A, puede reescribirse como:
public class B implements A{
public ConcreteA concreteA;
public void a(){
concreteA.a();
}
}
que no requiere herencia para anular el método, ¿es eso cierto?
virtual
palabra clave para admitir anulaciones. Entonces, el tema se hace más (o menos) ambiguo por los detalles del lenguaje.final
modificador existe exactamente para situaciones como estas. Si el comportamiento del método es tal que no está diseñado para ser anulado, márquelo comofinal
. De lo contrario, espere que los desarrolladores puedan optar por anularlo.Respuestas:
No, no es un código de olor.
Está dentro de las responsabilidades de cada clase considerar cuidadosamente si la subclasificación es apropiada y qué métodos pueden ser anulados .
La clase puede definirse a sí misma o cualquier método como final, o puede imponer restricciones (modificadores de visibilidad, constructores disponibles) sobre cómo y dónde se subclasifica.
El caso habitual para reemplazar los métodos es una implementación predeterminada en la clase base que se puede personalizar u optimizar en la subclase (especialmente en Java 8 con el advenimiento de los métodos predeterminados en las interfaces).
Una alternativa para anular el comportamiento es el Patrón de estrategia , donde el comportamiento se abstrae como una interfaz y la implementación se puede establecer en la clase.
fuente
A
implementarseDescriptionProvider
?A
podría implementarseDescriptionProvider
y, por lo tanto, ser el proveedor de descripción predeterminado. Pero la idea aquí es la separación de preocupaciones:A
necesita la descripción (algunas otras clases también podrían hacerlo), mientras que otras implementaciones las proporcionan.Sí, en general, anular los métodos concretos es un olor a código.
Debido a que el método base tiene un comportamiento asociado que los desarrolladores generalmente respetan, cambiar eso conducirá a errores cuando su implementación haga algo diferente. Peor aún, si cambian el comportamiento, su implementación correcta anterior puede exacerbar el problema. Y en general, es bastante difícil respetar el Principio de sustitución de Liskov para métodos concretos no triviales.
Ahora, hay casos en que esto se puede hacer y es bueno. Los métodos semi-triviales se pueden anular de manera segura. Métodos de base que existen sólo como un "defecto cuerdo" tipo de comportamiento que se pretende para ser montado sobre-tienen algunos usos buenos.
Por lo tanto, esto me parece un olor: a veces bueno, generalmente malo, eche un vistazo para asegurarse.
fuente
Number
clase con unincrement()
método que establece el valor en su próximo valor válido. Si lo subclasifica enEvenNumber
dondeincrement()
agrega dos en lugar de uno (y el establecedor impone la uniformidad), la primera propuesta del OP requiere implementaciones duplicadas de cada método en la clase y su segundo requiere métodos de envoltura de escritura para llamar a los métodos en el miembro. Parte del propósito de la herencia es que las clases de niños aclaren en qué se diferencian de los padres al proporcionar solo los métodos que necesitan ser diferentes.Si puede reescribirse de esa manera, significa que tiene control sobre ambas clases. Pero entonces sabes si diseñaste la clase A para que se derive de esta manera y si lo hiciste, no es un olor a código.
Si, por otro lado, no tienes control sobre la clase base, no puedes volver a escribir de esa manera. Entonces, o bien la clase fue diseñada para derivarse de esta manera, en cuyo caso simplemente siga adelante, no es un olor a código, o no lo fue, en cuyo caso tiene dos opciones: buscar otra solución por completo, o seguir adelante de todos modos , porque el trabajo triunfa sobre la pureza del diseño.
fuente
abstract
; pero hay muchos casos en los que no tiene que ser así. 2) si se debe no ser anulado, debe serfinal
(no virtual). Pero todavía hay muchos casos en los que los métodos pueden ser anulados. 3) si el desarrollador posterior no lee los documentos, se dispararán a sí mismos en el pie, pero lo harán sin importar las medidas que implemente.Primero, permítanme señalar que esta pregunta solo es aplicable en ciertos sistemas de escritura. Por ejemplo, en la tipificación estructural y de tipificación de pato sistemas - una clase simplemente tener un método con el mismo nombre y la firma podría significar que clase era tipo compatible (al menos para ese método en particular, en el caso de escribir Duck).
Esto (obviamente) es muy diferente del ámbito de los lenguajes estáticamente tipados , como Java, C ++, etc. Además de la naturaleza del tipeo fuerte , como se usa tanto en Java y C ++, como en C # y otros.
Supongo que proviene de un entorno Java, donde los métodos deben marcarse explícitamente como finales si el diseñador del contrato tiene la intención de que no se anulen. Sin embargo, en lenguajes como C #, lo contrario es lo predeterminado. Los métodos son (sin ninguna decoración, palabras clave especiales) " sellados " (versión de C # de
final
), y deben declararse explícitamente comovirtual
si se les permitiera anularlos.Si una API o biblioteca se ha diseñado en estos lenguajes con un método concreto que se puede anular, entonces (al menos en algunos casos) debe tener sentido que se anule. Por lo tanto, no es un olor a código anular un
final
método no sellado / virtual / no concreto. (Esto supone, por supuesto, que los diseñadores de la API siguen las convenciones y marcan comovirtual
(C #) o marcan comofinal
(Java) los métodos apropiados para lo que están diseñando).Ver también
fuente
Sí, es porque puede llevar a llamar super anti-patrón. También puede desnaturalizar el propósito inicial del método, introducir efectos secundarios (=> errores) y hacer que las pruebas fallen. ¿Usarías una clase cuyas pruebas unitarias son rojas (fallando)? No lo haré
Iré aún más lejos al decir que el código de olor inicial es cuando un método no es
abstract
nifinal
. Porque permite alterar (anulando) el comportamiento de una entidad construida (este comportamiento puede perderse o modificarse). Conabstract
yfinal
la intención es inequívoca:La forma más segura de agregar comportamiento a una clase existente es lo que muestra su último ejemplo: usar el patrón decorador.
fuente
Object.toString()
en Java ... Si esto fuera asíabstract
, muchas cosas predeterminadas no funcionarían, ¡y el código ni siquiera se compilaría cuando alguien no haya anulado eltoString()
método! Si lo fuerafinal
, las cosas serían aún peores: no hay capacidad para permitir que los objetos se conviertan en cadenas, excepto por la forma Java. Como habla la respuesta de @Peter Walser , las implementaciones predeterminadas (comoObject.toString()
) son importantes. Especialmente en los métodos predeterminados de Java 8.toString()
fueraabstract
ofinal
sería un dolor. Mi opinión personal es que este método no funciona y hubiera sido mejor no tenerlo (como equals y hashCode ). El propósito inicial de los valores predeterminados de Java es permitir la evolución de la API con retrocompatibilidad.