Hola, soy Luis y aquí les traigo un nuevo artículo.
La programación es divertida hasta que tiene que incorporar un nuevo requisito que cambia todo el diseño de su código. Todo el mundo escribe código, pero es demasiado difícil escribir un código que sus colegas puedan entender. Es difícil escribir un código que apesta menos y no sucumbe a los cambios. Sin embargo, hay algunos principios de diseño que pueden aprenderse, practicarse y asimilarse para que nos haga chupar menos.
Los principios de diseño son pautas básicas que nos ayudan a evitar un mal diseño orientado a los objetos. Nadie va a apuntar un arma hacia ti y hacer que te adhieras a ella estrictamente. No es una regla empírica pero definitivamente te ayudará. Ahora vamos a ver tres principios.
- Encapsula lo que varía.
- Favorece la composición sobre la herencia.
- Programa a interfaz.
Índice
Encapsula lo que varía
El principio es tan simple. Identifique los aspectos del código que varían y aíslelo con lo que permanece igual. Tienes que preguntarte
«¿Este código cambia cada vez que obtengo el nuevo requisito?»
Si la respuesta es sí, entonces ya sabe qué hacer. Encapsular. Veamos un ejemplo.
public void connectToDatabase(String connectionType) { DBConnection connection = null; if ("mysql".equals(connectionType)) { connection = new MySqlConnection(); } else if("mongodb".equals(connectionType)){ connection = new MongoConnection(); }else if("postgres".equals(connectionType)){ connection = new PostgresConnection(); } connection.createConnection(); }
Apuesto a que cada uno de nosotros ha visto un código como este y quién sabe si aún tenemos ese código funcionando en producción. Así que, digamos que necesitamos añadir una base de datos Oracle en nuestro código. ¿Cómo vamos a añadirla? Otra condición, ¿no? Pero digamos que hay un montón de otros códigos importantes en el mismo archivo y al añadir una nueva base de datos Oracle, por error se tocaron otros códigos de producción en el mismo archivo que introdujeron muchos errores más nuevos. Si hubiera sido encapsulado, entonces habría habido un solo lugar para cambiar. Al encapsularlo, nuestro diseño será mucho más flexible.
La parte que cambiará con cada nuevo requerimiento son las condiciones «if-else». Así que podemos crear una clase de fábrica que será la única responsable de crear las conexiones de la base de datos. No mostraré cómo crear una clase de fábrica, pero mostraré cómo se ve el código una vez que encapsulamos toda la lógica dentro de la clase de fábrica.
public void connectToDatabase(String connectionType) { DBConnectionFactory dbConnectionFactory = new DBConnectionFactory(); DBConnection connection = dbConnectionFactory.getConnection(connectionType); connection.createConnection(); }
Con este enfoque, digamos que si quieres añadir una conexión de base de datos H2, entonces tienes que cambiar sólo en la clase DBConnectionFactory.
Favorecer la composición sobre la herencia
En la programación orientada a objetos (OOP), la herencia es una relación IS-A (un automóvil es un vehículo) mientras que la composición es una relación HAS-A (un automóvil tiene un motor).
La herencia proporciona una excelente manera de reutilizar el código, pero se vuelve menos efectiva una vez que crece la jerarquía. Las clases y los objetos creados mediante herencia son estrechamente acoplado porque si algo se cambia en la clase base, afectará a sus subclases. Pero no es cierto para la composición. Las clases y objetos creados a través de la composición son débilmente acoplado. Hay otro gran beneficio de usar la composición sobre la herencia. La herencia no se puede probar fácilmente porque para probar la clase derivada también se necesita una superclase, pero la composición es como podemos usar objetos simulados.
Programa a la interfaz
El término interfaz aquí no significa simplemente la palabra clave que tenemos en el lenguaje de programación Java. Es un tipo de contrato que tiene un montón de reglas que deben ser seguidas por clases concretas que lo implementen. A menudo se le llama como programa para el supertipo. Si programamos en una interfaz en lugar de implementaciones concretas, proporciona la flexibilidad para explotar el polimorfismo.
La interfaz proporciona reglas y no le importa quién la implemente ni cómo se ve la implementación. Tomemos un ejemplo sencillo del mundo real.
interface FileUpload { void upload(Object o); }
Queremos subir el archivo a nuestra aplicación. Puede haber muchas formas diferentes de hacerlo. Podemos usar Cloudinary, AWS S3 bucket, upload care, etc. Entonces, las implementaciones de FileUpload se verían como AmazonS3FileUpload, CloudinaryFileUpload, UploadCareFileUpload, etcétera.
Programemos para interactuar.
FileUpload fileUpload = new CloudinaryFileUpload(); // code related to fileupload
Genial, nuestra función de carga de archivos funciona correctamente y, después de un año, queremos cambiar la carga de archivos de Cloudinary a AmazonS3. Solo necesitamos cambiar la implementación como:
FileUpload fileUpload = new AmazonS3FileUpload();
No romperá otros códigos porque confiamos en la interfaz y los métodos proporcionados por ella. Solo podemos usar métodos definidos dentro de la interfaz FileUpload. Si hubiera sido una implementación concreta con un montón de métodos adicionales, entonces habría creado un caos.
//program to concrete implementation CloudinaryFileUpload fileUpload = new CloudinaryFileUpload(); //code related to fileUpload
Por lo tanto, siempre es una buena idea programar en una interfaz. Además de la flexibilidad que proporciona, también es fácil de probar. Podemos simular fácilmente la interfaz FileUpload siempre que sea necesario.
Muchas gracias por leer mi artículo. Si tiene alguna sugerencia, será bienvenida.
Añadir comentario