Por ejemplo:
var duckBehaviors = new Duckbehavior();
duckBehaviors.quackBehavior = new Quack();
duckBehaviors.flyBehavior = new FlyWithWings();
Duck mallardDuck = new Duck(DuckTypes.MallardDuck, duckBehaviors)
Como la clase Duck contiene todos los comportamientos (abstractos), no parece necesario crear una nueva clase MallardDuck
(que se extienda Duck
).
Referencia: Head First Design Pattern, Capítulo 1.
design-patterns
dependency-injection
strategy-pattern
Deepak Mishra
fuente
fuente
Duckbehavior.quackBehavior
y otros campos en su código?Respuestas:
Claro, pero llamamos a eso composición y delegación . El patrón de estrategia y la inyección de dependencia pueden parecer estructuralmente similares, pero sus intenciones son diferentes.
El patrón de estrategia permite la modificación del comportamiento en tiempo de ejecución bajo la misma interfaz. Podría decirle a un pato silvestre que vuele y verlo volar con alas. Luego cámbielo por un pato piloto y véalo volar con Delta Airlines. Hacer eso mientras el programa se está ejecutando es una cuestión de Patrón de estrategia.
La inyección de dependencias es una técnica para evitar dependencias de codificación dura para que puedan cambiar de forma independiente sin requerir que los clientes sean modificados cuando cambian. Los clientes simplemente expresan sus necesidades sin saber cómo se satisfarán. Por lo tanto, cómo se cumplen se decide en otro lugar (generalmente en main). No necesitas dos patos para hacer uso de esta técnica. Simplemente algo que usa un pato sin saber o importar qué pato. Algo que no construye el pato o lo busca, pero está feliz de usar cualquier pato que le des.
Si tengo una clase de pato concreta, puedo hacer que implemente su comportamiento de vuelo. Incluso podría hacer que cambie los comportamientos de volar con alas a volar con Delta en función de una variable de estado. Esa variable podría ser un booleano, un int, o podría tener
FlyBehavior
unfly
método que haga cualquier estilo de vuelo sin que tenga que probarlo con un if. Ahora puedo cambiar los estilos de vuelo sin cambiar los tipos de pato. Ahora los ánades reales pueden convertirse en pilotos. Esto es composición y delegación . El pato está compuesto por un FlyBehavior y puede delegarle solicitudes de vuelo. Puede reemplazar todos sus comportamientos de pato de una vez de esta manera o mantener algo para cada comportamiento o cualquier combinación intermedia.Esto le da a todos los mismos poderes que tiene la herencia, excepto uno. La herencia le permite expresar qué métodos de Duck está anulando en los subtipos de Duck. La composición y la delegación requieren que Duck delegue explícitamente los subtipos desde el principio. Esto es mucho más flexible, pero implica más tipeo del teclado y Duck tiene que saber que está sucediendo.
Sin embargo, muchas personas creen que la herencia debe diseñarse explícitamente desde el principio. Y que si no lo ha sido, debe marcar sus clases como selladas / finales para no permitir la herencia. Si toma esa opinión, la herencia realmente no tiene ventaja sobre la composición y la delegación. Porque entonces, de cualquier manera, debe diseñar la extensibilidad desde el principio o estar dispuesto a derribar las cosas más adelante.
Derribar cosas es en realidad una opción popular. Solo tenga en cuenta que hay casos en los que es un problema. Si ha implementado de forma independiente bibliotecas o módulos de código que no tiene la intención de actualizar con la próxima versión, puede terminar atrapado con versiones de clases que no saben nada sobre lo que está haciendo ahora.
Si bien estar dispuesto a derribar las cosas más tarde puede liberarlo de un diseño excesivo, hay algo muy poderoso en poder diseñar algo que use un pato sin tener que saber qué hará realmente el pato cuando se use. Que no saber es algo poderoso. Te permite dejar de pensar en patos por un tiempo y pensar en el resto de tu código.
"Podemos" y "deberíamos" son preguntas diferentes. Favorecer la composición sobre la herencia no dice nunca usar la herencia. Todavía hay casos en los que la herencia tiene más sentido. Te mostraré mi ejemplo favorito :
La herencia le permite crear excepciones con nombres descriptivos más específicos en una sola línea.
Intenta hacerlo con la composición y obtendrás un desastre. Además, no hay riesgo del problema de herencia yo-yo porque no hay datos o métodos aquí para reutilizar y fomentar el encadenamiento de herencia. Todo esto agrega es un buen nombre. Nunca subestimes el valor de un buen nombre.
fuente
LoginException
no es un buen nombre. Es el clásico "nombre de pitufo" y, si lo quitoException
, todo lo que tengo esLogin
que no me dice nada de lo que salió mal.Exception
fin a cada clase de excepción, pero por favor no confunda "atascado" con "bueno".StackOverflow
,OutOfMemory
,NullReferenceAccess
,LoginFailure
etc Básicamente, toman "excepción" en el nombre. Y, si es necesario, corríjalos para que describa lo que salió mal.Puede reemplazar casi cualquier metodología con cualquier otra metodología y aún así producir software de trabajo. Sin embargo, algunos se ajustan mejor a un problema particular que otros.
Depende de muchas cosas cuál es preferible. Técnica anterior en la aplicación, experiencia en el equipo, desarrollos futuros esperados, preferencia personal y cuán difícil será para un recién llegado entenderlo, por nombrar algunos.
A medida que adquieras más experiencia y luches con más frecuencia con las creaciones de otras personas, es probable que pongas más énfasis en la última contemplación.
La herencia sigue siendo una herramienta de modelado válida y sólida que no siempre es necesariamente la más flexible, pero ofrece una guía sólida a las personas nuevas que pueden estar agradecidas por la asignación clara al dominio del problema.
fuente
La herencia no es tan importante como se pensaba. Todavía es importante , y eliminarlo sería un grave error.
Un ejemplo extremo es Objective-C, donde todo es una subclase de NSObject (el compilador en realidad no le permite declarar una clase que no tiene una clase base, por lo que todo lo que no está integrado en el compilador debe ser una subclase de algo integrado en el compilador). Hay muchas cosas útiles integradas en NSObject que no debería perderse.
Otro ejemplo interesante es UIView, la clase básica de "vista" en UIKit para el desarrollo de iOS. Esta es una clase que, por un lado, es un poco como una clase abstracta que declara la funcionalidad que las subclases deben completar, pero también es útil por sí sola. Tiene subclases suministradas por UIKit, que a menudo se usan tal cual. Existe una composición del desarrollador que instala subvistas en una vista. Y a menudo hay subclases definidas por el desarrollador, que a menudo usan composición. No hay una sola regla estricta o incluso reglas, usted usa lo que mejor se adapte a sus necesidades.
fuente
type
, todos los no-primitivo en hereda de JavaObject
, JavaScript todo en un prototipo ha terminando conObject
, etc[Arriesgaré una respuesta descarada a la pregunta titular.]
Sí ... excepto que el patrón de estrategia en sí usa herencia. El patrón de estrategia funciona en la herencia de la interfaz. Reemplazar la herencia con composición + estrategia mueve la herencia a un lugar diferente. No obstante, a menudo vale la pena realizar dicho reemplazo, ya que permite separar las jerarquías .
fuente
interface
diseño craptastic arreglado con herencia. El problema oculto aquí es la implementación inconsistente que transforma el comportamiento dependiente. PD. herencia e interfaz no son mutuamente excluyentes,interface
No. Si un pato silvestre requiere parámetros diferentes para crear una instancia que otro tipo de pato, sería una pesadilla cambiarlo. ¿También deberías poder crear una instancia de un pato? Es más un pato real o un pato mandarín o cualquier otro tipo de pato que tengas.
También es posible que desee algo de lógica en torno a los tipos para que una jerarquía de tipos sea mejor.
Ahora, si la reutilización del código se vuelve problemática para algunas de las funciones, podríamos componerla pasando las funciones a través del constructor.
Nuevamente, realmente depende de su caso de uso. Si solo tiene un pato y un pato silvestre, una jerarquía de clases es una solución mucho más simple.
Pero aquí hay un ejemplo en el que le gustaría usar el patrón de estrategia: si tiene clases de clientes y desea pasar una estrategia de facturación (interfaz) que podría ser una estrategia de facturación de hora feliz o una estrategia de facturación regular, puede pasarla en el constructor De esta manera, no tiene que hacer dos clases diferentes de clientes y no necesita una jerarquía. Solo tienes una clase de cliente.
fuente