Al diseñar el código, siempre tiene dos opciones.
- solo hazlo, en cuyo caso prácticamente cualquier solución funcionará para ti
- Sea pedante y diseñe una solución que explote las peculiaridades del lenguaje y su ideología (en este caso, los lenguajes OO: el uso del polimorfismo como un medio para tomar la decisión)
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 ifs que están en un código no tienen que estar allí. Naturalmente, si desea comparar dos tipos escalares, como ints o floats, es probable que tenga un if, 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 (el ifs) 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:
datano está encriptado ni comprimido (no llamar a nada, regresar data)
dataestá comprimido (llamar compress(data)y devolverlo)
dataestá cifrado (llame encrypt(data)y devuélvalo)
dataestá comprimido y encriptado (llame encrypt(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.
Este no es un lenguaje concreto. Es un enfoque genérico, cualquier palabra clave que esté allí para representarla puede ser de cualquier tipo, en un lenguaje como C # puede reemplazarlo con genéricos ( <T>).
Voy a suponer que en este momento puede tener dos clases responsables de la compresión y el cifrado.
class Compression
{
Compress(data : any) : any { ... }
}
class Encryption
{
Encrypt(data : any) : any { ... }
}
En un mundo empresarial, es muy probable que incluso estas clases específicas sean reemplazadas por interfaces, como la classpalabra clave sería reemplazada por interface(si se trata de lenguajes como C #, Java y / o PHP) o la classpalabra clave permanecería, pero Compressy los Encryptmétodos se definirían como un virtual puro , si se codifica en C ++.
Para hacer un adaptador, definimos una interfaz común.
interface DataProcessing
{
Process(data : any) : any;
}
Luego tenemos que proporcionar implementaciones de la interfaz para que sea útil.
// when neither encryption nor compression is enabled
class DoNothingAdapter : DataProcessing
{
public Process(data : any) : any
{
return data;
}
}
// when only compression is enabled
class CompressionAdapter : DataProcessing
{
private compression : Compression;
public Process(data : any) : any
{
return this.compression.Compress(data);
}
}
// when only encryption is enabled
class EncryptionAdapter : DataProcessing
{
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(data);
}
}
// when both, compression and encryption are enabled
class CompressionEncryptionAdapter : DataProcessing
{
private compression : Compression;
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(
this.compression.Compress(data)
);
}
}
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 Processmetodo.
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 DataProcessinginterfaz que diseñamos anteriormente.
class DataService
{
private dataProcessing : DataProcessing;
public DataService(dataProcessing : DataProcessing)
{
this.dataProcessing = dataProcessing;
}
}
El proceso en sí podría ser tan simple como esto:
public ComplicatedProcess(data : any) : any
{
data = this.dataProcessing.Process(data);
// ... perhaps work with the data
return data;
}
No más condicionales. La clase DataServiceno tiene idea de lo que realmente se hará con los datos cuando se pasan al dataProcessingmiembro, 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é ifs 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 Compressiony las Encryptionclases 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 ify else, que, en mi opinión, son incluso un poco peores que una simple if.
Tenga en cuenta que esta es mi forma de hacer un buen diseño de OO. Codificación para interfaces en lugar de implementaciones, así es como lo he hecho durante los últimos años y es el enfoque con el que me siento más cómodo.
Personalmente, me gusta más la programación if-less y agradecería mucho más la solución más larga en las 5 líneas de código. Es la forma en que estoy acostumbrado a diseñar código y me siento muy cómodo al leerlo.
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.
ifdeclaraciones?