¿Por qué es beneficioso usar el patrón de estrategia si solo puede escribir su código en casos if / then?
Por ejemplo: tengo una clase TaxPayer, y uno de sus métodos calcula los impuestos usando diferentes algoritmos. Entonces, ¿por qué no puede tener casos if / then y descubrir qué algoritmo usar en ese método, en lugar de usar el patrón de estrategia? Además, ¿por qué no puede simplemente implementar un método separado para cada algoritmo en la clase TaxPayer?
Además, ¿qué significa que el algoritmo cambie en tiempo de ejecución?
Respuestas:
Por un lado, grandes grupos de
if/else
bloques no son fácilmente comprobables . Cada nueva "rama" agrega otra ruta de ejecución y, por lo tanto, aumenta la complejidad ciclomática . Si desea probar su código a fondo, tendría que cubrir todas las rutas de ejecución, y cada condición requeriría que escriba al menos una prueba más (suponiendo que escriba pruebas pequeñas y enfocadas). Por otro lado, las clases que implementan estrategias suelen exponer solo 1 método público, que es fácil de probar.Entonces, con anidado
if/else
, terminará con muchas pruebas para una sola parte de su código, mientras que con Estrategia tendrá pocas pruebas para cada una de las estrategias más simples. Con este último, es fácil tener una mejor cobertura, porque es más difícil pasar por alto las rutas de ejecución.En cuanto a la extensibilidad , imagine que está escribiendo un marco, donde se supone que los usuarios pueden inyectar su propio comportamiento. Por ejemplo, desea crear algún tipo de marco de cálculo de impuestos y desea apoyar los sistemas fiscales de diferentes países. En lugar de implementarlos todos, solo desea dar a los usuarios del marco la oportunidad de proporcionar una implementación de cómo calcular algunos impuestos particulares.
Aquí está el patrón de estrategia:
TaxCalculation
, y su marco acepta instancias de este tipo para calcular impuestosNo puede hacer lo mismo
if/else
, porque eso requeriría cambiar el código del marco, en cuyo caso ya no sería un marco. Dado que los marcos a menudo se distribuyen en forma compilada, esta puede ser la única opción.Aún así, incluso si solo escribe un código regular, la Estrategia es beneficiosa porque aclara sus intenciones. Dice "esta lógica es conectable y condicional", es decir, puede haber múltiples implementaciones que pueden variar según las acciones del usuario, la configuración o incluso la plataforma.
El uso del patrón de estrategia puede mejorar la legibilidad porque, si bien una clase que implementa una estrategia particular generalmente debe tener un nombre descriptivo, por ejemplo
USAIncomeTaxCalculator
, losif/else
bloques no tienen nombre, en el mejor de los casos solo se comentan, y los comentarios pueden mentir. Además, para mi gusto personal, solo tener más de 3if/else
bloques seguidos no es legible, y se vuelve bastante malo con los bloques anidados.El principio Abierto / Cerrado también es muy relevante porque, como describí en el ejemplo anterior, la Estrategia le permite extender una lógica en algunas partes de su código ("abierto para extensión") sin reescribir esas partes ("cerrado para modificación" )
fuente
if/else
Los bloques también reducen la legibilidad del código. En cuanto al patrón de estrategia, vale la pena mencionar el principio Abierto / Cerrado de la OMI.if
s tenga, más rutas posibles existen a través de su código, más pruebas tiene que escribir y más formas de que ese método falle. Si puedo citar al difunto Yogi Berra: "Si llegas a una bifurcación en el camino, tómalo". Esto se aplica brillantemente a las pruebas unitarias. Además, muchasif
declaraciones significan que es probable que repita la lógica para esas condiciones, lo que aumenta aún más su carga de prueba y aumenta el riesgo de que surjan errores.if/else
bloques para llamarlos (o dentro de ellos, para determinar si debería hacer algo o no), por lo que no es de mucha ayuda, excepto quizás un código más legible. Y tampoco hay extensibilidad para los usuarios de su marco hipotético.A veces deberías usar if / then. Es un código simple que es fácil de leer.
Los dos problemas clave con el simple código if / then es que puede violar el principio abierto cerrado . Si alguna vez tiene que entrar y agregar o cambiar una condición, está modificando este código. Si espera tener más condiciones, solo agregar una nueva estrategia es más simple / más limpio / menos probable que se rompa.
El otro problema es el acoplamiento. Al usar if / then, todas las implementaciones están vinculadas a esa implementación, lo que hace que sea más difícil cambiarlas en el futuro. Al usar la estrategia, el único acoplamiento es a la interfaz de la estrategia.
fuente
La estrategia es útil cuando las
if/then
condiciones se basan en tipos , como se explica en http://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.htmlLos condicionales de verificación de tipo generalmente no tienen una alta complejidad ciclomática, por lo que no diría que la Estrategia mejora las cosas necesariamente allí.
La razón principal de la Estrategia se explica en el libro GoF p.316 que introdujo el patrón:
Como se menciona en otras respuestas, si se aplica adecuadamente, el patrón de Estrategia permite agregar nuevas extensiones (estrategias concretas) sin necesariamente requerir modificaciones del resto del código. Este es el llamado principio abierto-cerrado o principio de variaciones protegidas . Por supuesto, aún debe codificar la nueva estrategia concreta, y el código del cliente debe reconocer las estrategias como complementos (esto no es trivial).
Con
if/then
condicionales, es necesario cambiar el código de la clase que contiene la lógica condicional. Como se menciona en otras respuestas, a veces esto está bien cuando no desea agregar la complejidad para admitir agregar nuevas funcionalidades (complementos) sin volver a compilar.fuente
Ese es exactamente el mayor beneficio del patrón estratégico. No tener condiciones.
Desea que sus clases / métodos / funciones sean lo más simples y breves posible. El código corto es muy fácil de probar y muy fácil de leer.
Las condiciones (
if
/elseif
/else
) hacen que sus clases / métodos / funciones sean largos, porque generalmente el código donde se evalúa una decisióntrue
es diferente de la parte donde se evalúa la decisiónfalse
.Otro gran beneficio del patrón de estrategia es que es reutilizable en todo su proyecto.
Al usar el patrón de diseño de la estrategia, es muy probable que tenga algún tipo de contenedor de IoC, del cual obtiene la implementación deseada de una interfaz, tal vez por un
getById(int id)
método, dondeid
podría ser un miembro enumerador.Esto significa que la creación de la implementación es solo en un lugar de su código.
Si desea agregar más implementaciones, agregue la nueva implementación al
getById
método y este cambio se refleja en todas partes en el código donde lo llama.Con
if
/elseif
/else
esto es imposible de hacer. Al agregar una nueva implementación, debe agregar un nuevoelseif
bloque y hacerlo en todas partes donde se usaron las implementaciones, o podría terminar con un código que no es válido, porque olvidó agregar la implementación a su estructura.En mi ejemplo,
id
podría ser una variable que se rellena en función de una entrada del usuario. Si el usuario hace clic en un botón A, entoncesid = 2
, si hace clic en un botón B, entoncesid = 8
.Debido al
id
valor diferente , se obtiene una implementación diferente de una interfaz del contenedor IoC y el código realiza diferentes operaciones.fuente
if
/elseif
/else
estado. Igual que antes, solo en un lugar diferente.id
variable en elgetById
método, que devolvería la implementación específica. Cada vez que necesite una implementación de la interfaz, le pedirá al contenedor de IoC que se la entregue.getSortByEnumType(SortEnum type)
devuelva una implementación de unaSort
interfaz, un método quegetSortType
devuelva unaSortEnum
variable y tome una colección como parámetro, y elgetSortByEnumType
método nuevamente contendrá un interruptor en eltype
parámetro que le devolverá el algoritmo de clasificación correcto. Si necesita agregar un nuevo algoritmo de clasificación, solo necesita editar la enumeración y un método. Y estás listo.El Patrón de estrategia le permite separar sus algoritmos (los detalles) de su lógica empresarial (política de alto nivel). Estas dos cosas no solo son confusas de leer cuando se mezclan, sino que también tienen razones muy diferentes para cambiar.
También hay un factor importante de escalabilidad del trabajo en equipo aquí. Imagine un gran equipo de programación donde muchas personas están trabajando en este paquete de contabilidad. Si todos los algoritmos de impuestos están en la clase o módulo TaxPayer , entonces los conflictos de fusión se vuelven probables. Los conflictos de fusión requieren mucho tiempo y son propensos a errores. Esta vez, la productividad del equipo se debilita y los errores introducidos por las malas fusiones dañan la credibilidad con los clientes.
Un algoritmo que cambia en tiempo de ejecución es aquel cuyo comportamiento está determinado por la configuración o el contexto. Un enfoque if / then en el lugar no permite esto de manera efectiva, ya que implica volver a cargar las clases existentes utilizadas activamente. Con el Patrón de estrategia, los objetos de estrategia que implementan cada algoritmo podrían construirse en uso. Como resultado, los cambios en estos algoritmos (correcciones de errores o mejoras) podrían realizarse y recargarse en tiempo de ejecución. Este enfoque podría usarse para permitir la disponibilidad continua y las versiones de tiempo de inactividad cero.
fuente
No hay nada malo en
if/else
sí mismo. En muchos casosif/else
es la forma más simple y legible de expresar la lógica. Por lo tanto, el enfoque que describe es perfectamente válido en muchos casos. (También es perfectamente comprobable, por lo que no es un problema).Pero hay algunos casos particulares en los que un patrón de estrategia puede mejorar la capacidad de mantenimiento del código general. Por ejemplo:
Para que el patrón de estrategia tenga sentido, la interfaz entre la lógica central y los algoritmos de cálculo de impuestos debe ser más estable que los componentes individuales. Si es igual de probable que un cambio de requisitos haga que cambie la interfaz, entonces el patrón de estrategia podría ser realmente una responsabilidad.
Todo se reduce a si los "algoritmos de cálculo de impuestos" se pueden separar limpiamente de la lógica central que lo invoca. Un patrón de estrategia tiene algunos gastos generales en comparación con un
if/else
, por lo que tendrá que decidir caso por caso si la inversión vale la pena.fuente