Voy a guardar una carga útil de cadena en la base de datos. Tengo dos configuraciones globales:
- cifrado
- compresión
Estos pueden habilitarse o deshabilitarse utilizando la configuración de forma que solo uno de ellos esté habilitado, ambos estén habilitados o ambos estén deshabilitados.
Mi implementación actual es esta:
if (encryptionEnable && !compressEnable) {
encrypt(data);
} else if (!encryptionEnable && compressEnable) {
compress(data);
} else if (encryptionEnable && compressEnable) {
encrypt(compress(data));
} else {
data;
}
Estoy pensando en el patrón Decorador. ¿Es la elección correcta, o tal vez hay una mejor alternativa?
design
design-patterns
object-oriented-design
refactoring
Damith Ganegoda
fuente
fuente
if
declaraciones?Respuestas:
Al diseñar el código, siempre tiene dos opciones.
No me voy a centrar en el primero de los dos, porque realmente no hay nada que decir. Si solo desea que funcione, puede dejar el código tal como está.
Pero, ¿qué pasaría si eliges hacerlo de forma pedante y realmente resuelves el problema con los patrones de diseño, de la manera que quisieras?
Podrías estar mirando el siguiente proceso:
Al diseñar el código OO, la mayoría de los
if
s que están en un código no tienen que estar allí. Naturalmente, si desea comparar dos tipos escalares, comoint
s ofloat
s, es probable que tenga unif
, pero si desea cambiar los procedimientos en función de la configuración, puede usar el polimorfismo para lograr lo que desea, mover las decisiones (elif
s) de su lógica de negocios a un lugar, donde se crean instancias de objetos, a fábricas .A partir de ahora, su proceso puede pasar por 4 caminos separados:
data
no está encriptado ni comprimido (no llamar a nada, regresardata
)data
está comprimido (llamarcompress(data)
y devolverlo)data
está cifrado (llameencrypt(data)
y devuélvalo)data
está comprimido y encriptado (llameencrypt(compress(data))
y devuélvalo)Simplemente mirando los 4 caminos, encuentras un problema.
Tiene un proceso que llama a 3 (en teoría 4, si cuenta no llamar a nada como uno) diferentes métodos que manipulan los datos y luego los devuelve. Los métodos tienen diferentes nombres , diferentes llamadas API públicas (la forma en que los métodos comunican su comportamiento).
Usando el patrón del adaptador , podemos resolver el nombre de la colisión (podemos unir la API pública) que ha ocurrido. Simplemente dicho, el adaptador ayuda a dos interfaces incompatibles a trabajar juntas. Además, el adaptador funciona definiendo una nueva interfaz de adaptador, cuyas clases intentan unir su implementación API.
Voy a suponer que en este momento puede tener dos clases responsables de la compresión y el cifrado.
En un mundo empresarial, es muy probable que incluso estas clases específicas sean reemplazadas por interfaces, como la
class
palabra clave sería reemplazada porinterface
(si se trata de lenguajes como C #, Java y / o PHP) o laclass
palabra clave permanecería, peroCompress
y losEncrypt
métodos se definirían como un virtual puro , si se codifica en C ++.Para hacer un adaptador, definimos una interfaz común.
Luego tenemos que proporcionar implementaciones de la interfaz para que sea útil.
Al hacer esto, terminas con 4 clases, cada una haciendo algo completamente diferente, pero cada una de ellas brinda la misma API pública. El
Process
metodo.En su lógica de negocios, donde se ocupa de la decisión ninguno / cifrado / compresión / ambos, diseñará su objeto para que dependa de la
DataProcessing
interfaz que diseñamos anteriormente.El proceso en sí podría ser tan simple como esto:
No más condicionales. La clase
DataService
no tiene idea de lo que realmente se hará con los datos cuando se pasan aldataProcessing
miembro, y realmente no le importa, no es su responsabilidad.Idealmente, usted tendría pruebas unitarias que prueben las 4 clases de adaptadores que creó para asegurarse de que funcionen, usted hará pasar su prueba. Y si pasan, puede estar bastante seguro de que funcionarán sin importar dónde los llame en su código.
Entonces, al hacerlo de esta manera, ¿ya nunca tendré
if
s en mi código?No. Es menos probable que tenga condicionales en su lógica de negocios, pero aún así tienen que estar en algún lugar. El lugar son tus fábricas.
Y esto está bien. Separa las preocupaciones de la creación y de hecho usa el código. Si hace que sus fábricas sean confiables (en Java, incluso podría ir tan lejos como usar algo como el marco Guice de Google), en su lógica comercial no le preocupa elegir la clase correcta para inyectarse. Porque sabe que sus fábricas funcionan y entregarán lo que se le pide.
¿Es necesario tener todas estas clases, interfaces, etc.?
Esto nos lleva de vuelta al comienzo.
En OOP, si elige el camino para usar el polimorfismo, realmente quiere usar patrones de diseño, quiere explotar las características del lenguaje y / o quiere seguir que todo es una ideología de objeto, entonces lo es. Y aun así, este ejemplo no incluso mostrar todas las fábricas que van a necesitar y si se va a refactorizar los
Compression
y lasEncryption
clases y hacer que las interfaces en su lugar, usted tiene que incluir sus implementaciones también.Al final, terminas con cientos de pequeñas clases e interfaces, centradas en cosas muy específicas. Lo cual no es necesariamente malo, pero podría no ser la mejor solución para usted si todo lo que desea es hacer algo tan simple como sumar dos números.
Si desea hacerlo de manera rápida, puede tomar la solución de Ixrec , que al menos logró eliminar los bloques
else if
yelse
, que, en mi opinión, son incluso un poco peores que una simpleif
.Actualización 2: ha habido una discusión salvaje sobre la primera versión de mi solución. Discusión principalmente causada por mí, por lo cual me disculpo.
Decidí editar la respuesta de una manera que es una de las formas de ver la solución, pero no la única. También eliminé la parte del decorador, donde me refería a la fachada, que al final decidí dejar por completo, porque un adaptador es una variación de la fachada.
fuente
Compression
yEncryption
parecen totalmente superfluas. No estoy seguro de si estás sugiriendo que de alguna manera son necesarios para el proceso de decoración, o simplemente implicando que representan conceptos extraídos. El segundo problema es que hacer una clase comoCompressionEncryptionDecorator
lleva al mismo tipo de explosión combinatoria que los condicionales del OP. Tampoco veo el patrón decorador con suficiente claridad en el código sugerido.El único problema que veo con su código actual es el riesgo de explosión combinatoria a medida que agrega más configuraciones, que pueden mitigarse fácilmente estructurando el código más de esta manera:
No conozco ningún "patrón de diseño" o "modismo" del que esto pueda considerarse un ejemplo.
fuente
else
entre mis dos declaraciones if, y por qué estoy asignandodata
cada vez. Si ambas marcas son verdaderas, comprimir () se ejecuta, y encriptar () se ejecuta en el resultado de comprimir (), tal como lo desea.Supongo que su pregunta no busca practicidad, en cuyo caso la respuesta de lxrec es la correcta, sino aprender sobre patrones de diseño.
Obviamente, el patrón de comando es una exageración para un problema tan trivial como el que propones, pero a modo de ilustración aquí va:
Como puede ver, poner los comandos / transformación en una lista permite ejecutarlos secuencialmente. Obviamente, ejecutará ambos, o solo uno de ellos dependerá de lo que ponga en la lista sin condiciones.
Obviamente, los condicionales terminarán en algún tipo de fábrica que reúne la lista de comandos.
EDITAR para el comentario de @ texacre:
Hay muchas formas de evitar las condiciones if en la parte de creación de la solución, tomemos por ejemplo una aplicación GUI de escritorio . Puede tener casillas de verificación para las opciones de compresión y cifrado. En el
on clic
caso de esas casillas de verificación, crea una instancia del comando correspondiente y lo agrega a la lista, o lo elimina de la lista si está deseleccionando la opción.fuente
commands.add(new EncryptCommand());
ocommands.add(new CompressCommand());
respectivamente.Creo que los "patrones de diseño" están innecesariamente orientados hacia los "patrones oo" y evitan completamente ideas mucho más simples. De lo que estamos hablando aquí es de una tubería de datos (simple).
Intentaría hacerlo en clojure. Cualquier otro lenguaje donde las funciones sean de primera clase probablemente también esté bien. Tal vez podría hacer un ejemplo de C # más adelante, pero no es tan bueno. Mi forma de resolver esto sería los siguientes pasos con algunas explicaciones para los no clojurianos:
1. Representar un conjunto de transformaciones.
Este es un mapa, es decir, una tabla de búsqueda / diccionario / lo que sea, desde palabras clave hasta funciones. Otro ejemplo (palabras clave para cadenas):
Entonces, escribir
(transformations :encrypt)
o(:encrypt transformations)
devolvería la función de cifrado. ((fn [data] ... )
es solo una función lambda).2. Obtenga las opciones como una secuencia de palabras clave:
3. Filtre todas las transformaciones utilizando las opciones proporcionadas.
Ejemplo:
4. Combina funciones en una:
Ejemplo:
5. Y luego juntos:
Los ÚNICOS cambios si queremos agregar una nueva función, digamos "debug-print", son los siguientes:
fuente
funcs-to-run-here (map options funcs)
está haciendo el filtrado, eligiendo así un conjunto de funciones para aplicar. Tal vez debería actualizar la respuesta y entrar un poco más en detalles.[Esencialmente, mi respuesta es una continuación de la respuesta de @Ixrec anterior . ]
Una pregunta importante: ¿va a crecer el número de combinaciones distintas que necesita cubrir? Conoces mejor tu dominio de materia. Este es tu juicio para hacer.
¿Puede crecer el número de variantes? Bueno, eso no es inconcebible. Por ejemplo, es posible que deba acomodar más algoritmos de cifrado diferentes.
Si anticipa que el número de combinaciones distintas va a crecer, el patrón de Estrategia puede ayudarlo. Está diseñado para encapsular algoritmos y proporcionar una interfaz intercambiable para el código de llamada. Aún tendrá una pequeña cantidad de lógica cuando cree (cree una instancia) la estrategia adecuada para cada cadena en particular.
Ha comentado anteriormente que no espera que los requisitos cambien. Si no espera que aumente el número de variantes (o si puede diferir esta refactorización), mantenga la lógica tal como está. Actualmente, tiene una pequeña y manejable cantidad de lógica. (Quizás se ponga una nota en los comentarios sobre una posible refactorización de un patrón de Estrategia).
fuente
Una forma de hacer esto en scala sería:
Usar el patrón de decorador para lograr los objetivos anteriores (separación de la lógica de procesamiento individual y cómo se conectan entre sí) sería demasiado detallado.
En el caso de que necesite un patrón de diseño para lograr estos objetivos de diseño en un paradigma de programación OO, el lenguaje funcional ofrece soporte nativo al usar funciones como ciudadanos de primera clase (líneas 1 y 2 en el código) y composición funcional (línea 3)
fuente