Por ejemplo, considere que tengo una clase para que otras clases extiendan:
public class LoginPage {
public String userId;
public String session;
public boolean checkSessionValid() {
}
}
y algunas subclases:
public class HomePage extends LoginPage {
}
public class EditInfoPage extends LoginPage {
}
De hecho, la subclase no tiene ningún método para anular, tampoco accedería a HomePage de manera genérica, es decir: no haría algo como:
for (int i = 0; i < loginPages.length; i++) {
loginPages[i].doSomething();
}
Solo quiero reutilizar la página de inicio de sesión. Pero de acuerdo con https://stackoverflow.com/a/53354 , preferiría la composición aquí porque no necesito la interfaz LoginPage, por lo que no uso la herencia aquí:
public class HomePage {
public LoginPage loginPage;
}
public class EditInfoPage {
public LoginPage loginPage;
}
pero el problema viene aquí, en la nueva versión, el código:
public LoginPage loginPage;
se duplica cuando se agrega una nueva clase. Y si LoginPage necesita setter y getter, se deben copiar más códigos:
public LoginPage loginPage;
private LoginPage getLoginPage() {
return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
this.loginPage = loginPage;
}
Entonces mi pregunta es, ¿la "composición sobre la herencia" está violando el "principio seco"?
inheritance
composition
dry
ocomfd
fuente
fuente
extends LoginPage
todas partes. ¡COMPRUEBE MATE!LoginPage
un decorador. No más duplicaciones, solo una simplepage = new LoginPage(new EditInfoPage())
y ya está. O usa el principio de abrir-cerrar para hacer que el módulo de autenticación se pueda agregar a cualquier página dinámicamente. Hay muchas maneras de lidiar con la duplicación de código, principalmente involucrando encontrar una nueva abstracción.LoginPage
es probable que sea un mal nombre, lo que quiere asegurarse es que el usuario esté autenticado mientras navega por esa página, mientras se lo redirigeLoginPage
o se le muestra un mensaje de error adecuado si no lo está.Respuestas:
Er espera, te preocupa que repitas
en dos lugares viola SECO? Por esa lógica
ahora solo puede existir en un objeto en toda la base de código. Bleh
SECO es algo bueno a tener en cuenta, pero vamos. Además
se está duplicando en su alternativa, por lo que incluso ser anal sobre DRY no tiene sentido.
Las preocupaciones válidas de DRY tienden a enfocarse en el comportamiento idéntico que se necesita en múltiples lugares que se definen en múltiples lugares, de modo que la necesidad de cambiar este comportamiento termina requiriendo un cambio en varios lugares. Tome decisiones en un solo lugar y solo tendrá que cambiarlas en un solo lugar. No significa que solo un objeto pueda contener una referencia a su página de inicio de sesión.
SECO no debe seguirse a ciegas. Si está duplicando porque copiar y pegar es más fácil que pensar en un buen método o nombre de clase, entonces probablemente esté equivocado.
Pero si desea poner el mismo código en un lugar diferente porque ese lugar diferente está sujeto a una responsabilidad diferente y es probable que deba cambiar de forma independiente, entonces probablemente sea conveniente relajar su aplicación de DRY y permitir que este comportamiento idéntico tenga una identidad diferente . Es el mismo tipo de pensamiento que entra en prohibir los números mágicos.
DRY no se trata solo de cómo se ve el código. Se trata de no difundir los detalles de una idea con una repetición sin sentido que obliga a los encargados del mantenimiento a arreglar las cosas utilizando la repetición sin sentido. Es cuando tratas de decirte a ti mismo que la repetición sin sentido es solo tu convención que las cosas van mal de verdad.
De lo que creo que realmente estás tratando de quejarte se llama código repetitivo. Sí, usar composición en lugar de herencia exige un código repetitivo. Nada queda expuesto de forma gratuita, debe escribir un código que lo exponga. Con ese estándar viene la flexibilidad de estado, la capacidad de estrechar la interfaz expuesta, dar nombres diferentes a las cosas que sean apropiadas para su nivel de abstracción, buena indirecta, y está utilizando lo que está compuesto desde el exterior, no el adentro, así que te enfrentas a su interfaz normal.
Pero sí, es una gran cantidad de teclado adicional. Siempre que pueda evitar el problema del yoyo de rebotar hacia arriba y hacia abajo en una pila de herencia mientras leo el código, vale la pena.
Ahora no es que me niegue a usar la herencia. Uno de mis usos favoritos es dar nuevos nombres a las excepciones:
fuente
int x;
no puede existir más de cero veces en una base de códigoint a; int b; int x;
que presionaré para que 'usted' sea eliminado.Un malentendido común con el principio DRY es que de alguna manera está relacionado con no repetir líneas de código. El principio DRY es "Cada conocimiento debe tener una representación autoritaria, inequívoca y única dentro de un sistema" . Se trata de conocimiento, no de código.
LoginPage
sabe cómo dibujar la página para iniciar sesión. SiEditInfoPage
supiera cómo hacerlo, sería una violación. La inclusión aLoginPage
través de la composición no constituye en modo alguno una violación del principio DRY.El principio DRY es quizás el principio más mal utilizado en ingeniería de software y siempre debe considerarse no como un principio para no duplicar el código, sino como un principio para no duplicar el conocimiento abstracto del dominio. En realidad, en muchos casos, si aplica DRY correctamente , duplicará el código , y esto no es necesariamente algo malo.
fuente
Respuesta corta: sí, en cierto grado menor y aceptable.
A primera vista, la herencia a veces puede ahorrarle algunas líneas de código, ya que tiene el efecto de decir "mi clase de reutilización contendrá todos los métodos y atributos públicos de una manera 1: 1". Entonces, si hay una lista de 10 métodos en un componente, uno no tiene que repetirlos en código en la clase heredada. Cuando en un escenario de composición, 9 de esos 10 métodos deben exponerse públicamente a través de un componente de reutilización, entonces uno debe anotar 9 llamadas delegadas y dejar salir el restante, no hay forma de evitarlo.
¿Por qué es esto tolerable? Mire los métodos que se replican en un escenario de composición: estos métodos delegan exclusivamente las llamadas a la interfaz del componente, por lo que no contienen lógica real.
El corazón del principio DRY es evitar tener dos lugares en el código donde se codifican las mismas reglas lógicas , porque cuando estas reglas lógicas cambian, en el código no DRY es fácil adaptar uno de esos lugares y olvidarse del otro, que introduce un error
Pero dado que delegar llamadas no contiene lógica, estas normalmente no están sujetas a tal cambio, por lo que no causan un problema real cuando se "prefiere la composición sobre la herencia". E incluso si la interfaz del componente cambia, lo que podría inducir cambios formales en todas las clases que usan el componente, en un lenguaje compilado el compilador nos dirá cuándo olvidamos cambiar una de las personas que llaman.
Una nota a su ejemplo: no sé cómo son su aspecto
HomePage
y el suyoEditInfoPage
, pero si tienen la funcionalidad de inicio de sesión y unHomePage
(o unEditInfoPage
) es unLoginPage
, entonces la herencia podría ser la herramienta correcta aquí. Un ejemplo menos discutible, donde la composición será la mejor herramienta de una manera más obvia, probablemente aclararía las cosas.Suponiendo que
HomePage
ni loEditInfoPage
son ni unoLoginPage
, y uno quiere reutilizar este último, como usted escribió, es muy probable que uno requiera solo algunas partes delLoginPage
, no todo. Si ese es el caso, un mejor enfoque que usar la composición de la manera mostrada podría serextraer la parte reutilizable de
LoginPage
en un componente propioreutilice ese componente
HomePage
y deEditInfoPage
la misma manera que se usa ahora dentroLoginPage
De esa manera, por lo general, se volverá mucho más claro por qué y cuándo la composición sobre la herencia es el enfoque correcto.
fuente