En programación orientada a objetos, ¿cuándo construir una clase?.

Cuando escribo código, hay un par de aspectos en los que cada vez invierto más tiempo. Pudieran ser considerados de poca importancia pero…

En programación orientada a objetos, ¿cuándo construir una clase?.

Cuando escribo código, hay un par de aspectos en los que cada vez invierto más tiempo. Pudieran ser considerados de poca importancia pero con el paso de los años cada vez me cuestan más y me parecen asuntos más complejos de resolver. Me estoy refiriendo al naming y a cuando implementar una clase. Hace varios años, cuando sabía menos aún y tenía menos experiencia, casi ni me paraba a pensar en las clases que creaba en el código ni en el naming que les asignaba tanto a ellas como a los atributos, métodos o variables que utilizaba. Parece que mientras más sé…más me cuesta todo. En fin, al turrón.

El tema del naming lo dejaremos para otra entrada del blog. En esta, bajo el paradigma de la programación orientada a objetos intentaré comentar algunos aspectos para tener más sencilla la respuesta a la pregunta, ¿cuándo se debe construir una clase?.

En la pequeña y humilde biblioteca que tengo en casa de libros sobre desarrollo de software, hay uno que me gusta mucho, es el CODE COMPLETE 2 de Steve McConnell. Es el típico libro que no me he leído del tirón pero del que si me he leído muchísimas partes una y otra vez y en el cual he buscado soluciones a problemas concretos. En esta ocasión, me he ayudado de un capítulo del libro, concretamente el “6.4. Reasons to create a class” que comienza en la página 152, por lo tanto, lo que vais a leer a continuación es una traducción libre y resumida por mi parte de dicho capítulo, de dicho libro y de dicho autor, por lo que cualquier corrección o aporte serán recibidos con enorme gratitud.

…si aceptas todo lo que has leído, puedes pensar que la única razón para crear una clase es modelar un objeto del mundo real. En la práctica, hay más razones para crearlas. A continuación, se presenta algunas buenas razones para crear una clase:
Modelar objetos del mundo real.
El modelado de objetos del mundo real no debe ser la única razón para crear una clase pero todavía continúa siendo una muy buena razón para crearlas. Crea una nueva clase para cada tipo de objeto del mundo real que tu programa modele, añádele los atributos necesarios y luego construye rutinas de servicio que modelen el comportamiento de dicho objeto.
Modelar objetos abstractos.
Otra buena razón para crear una clase es modelar un objeto abstracto, es decir, un objeto que no tenga una correspondencia con un objeto concreto del mundo real pero que proporcione una abstracción de otros objetos concretos. Aquí podríamos hablar del clásico objeto Figura. Círculo y Cuadrado existen realmente, pero la clase Figura sería una abstracción de otras formas específicas. En programación orientada a objetos, las abstracciones no suelen ser tan simples como el caso de la Figura. Las abstracciones son complejas y muchas veces resulta muy complicado resolverlas de forma adecuada ya que no es un proceso determinista lo que podría llevarnos a que se diera el caso de que varios diseñadores pudieran abstraer diferentes generalidades. Dar con abstracciones adecuadas de objetos es uno de los grandes desafíos en el diseño de orientación a objetos.
Reducir complejidad.
La razón más importante para crear una clase es reducir la complejidad de la aplicación. Crea una clase para esconder información de manera que no necesites volver a pensar en ella. Está claro que necesitarás pensar sobre la clase, qué hace y cómo lo hace cuando la estés escribiendo, pero después de escrita, deberías ser capaz de olvidar los detalles y usar dicha clase sin tener un conocimiento exhaustivo de como trabaja internamente. Otras razones para crear una clase reduciendo la complejidad es: minimizar el código o mejorar la mantenibilidad.
Aislar complejidad.
La complejidad en todas sus formas, ya sean complejos algoritmos, grandes conjuntos de datos, enrevesados protocolos de comunicaciones…etc, son propensos a generar errores. Si un error ocurre, debiera ser fácil encontrarlo si se encuentra localizado dentro de una clase y no esparcido por el código en varias clases. Si están aislados, los cambios resultantes de corregir dicho error no afectarán a otro código porque solo una clase debería ser corregida. O si por ejemplo, encuentras un algoritmo mejor que otro que yatengas implementado, este último será más fácil de actualizar si se encuentra aislado por completo en una clase.
Esconder detalles de implementación.
Esconder los detalles de una implementación es una buena razón para crear una clase. Ya sea esconder un complejo acceso a una base de datos o esconder algo más trivial como el almacenamiento de un dato ya sea como una cadena o valor numérico.
Limitar los efectos de cambios.
Aislar áreas de la aplicación que son propensas a cambios así como los efectos de dichos cambios deberían limitarse al alcance de una clase o como mucho a unas pocas clases. Diseñar de forma aislada las partes que más cambian hacen más fácil los cambios en ellas. Las partes que más suelen cambiar son aquellas relacionadas con las dependencias del hardware, entrada y salida, tipos de datos complejos y reglas de negocio.
Esconder datos globales.
Si necesitas usar datos globales, puedes esconder sus detalles de implementación detrás del interfaz de una clase. Trabajar con datos globales a través de métodos proporciona más beneficios que trabajar con ellos directamente ya que puedes cambiar la estructura de los datos sin cambiar tu aplicación o monitorear el acceso a dichos datos…etc.
Coordinar el paso de parámetros.
Si necesitas pasar parámetros a través de varios métodos, puede indicar que necesites refactorizar aquellos métodos en una clase que comparta el parámetro como un objeto de datos. Coordinar el paso de un parámetros no es un objetivo por si mismo, pero pasar muchos datos puede sugerir que organizarlos en una clase pueda hacer que se haga mejor trabajar con ellos. “Nota personal: Yo aquí he entendido siempre que a las clases que se refiere en este párrafo son los Data Transfer Object o DTO”.
Centralizar el control.
Es una buena idea controlar cada tarea en un único lugar. Dicho control puede ser de muchas formas: control de dispositivos, control de conexiones a la base de datos, impresoras…etc. Usar una clase para leer o escribir a una base de datos es, por ejemplo, otra forma centralizada de control. Si la base de datos necesita ser convertida a datos en memoria, los cambios solo afectarán a una clase.
Facilitar la reutilización de código.
El código puesto en clases bien construidas puede favorecer la reutilización de dicho código en otros programas de una forma mucho mejor que si ese mismo código estuviera embebido en una única gran clase. Incluso si hay una parte del código que es llamada desde un solo lugar de la aplicación y pertenece a una clase grande, puede tener sentido extraer y poner ese código en su propia clase si va a reutilizarse en otro programa.
Planificar para un grupo de aplicaciones.
Este apartado se refiere a código reutilizado en varias aplicaciones similares. Si esperas que una aplicación sea modificada, es buena idea aislar aquellas partes que esperas que cambien poniéndolas en sus propias clases. Podrás entonces modificar dichas clases sin afectar al resto del programa o podrás ponerlas en nuevas clases. No hay que pensar únicamente en los cambios de una sola aplicación sino en como afectará al resto de las aplicaciones. El autor expone el caso de como hace muchos años, él administraba un equipo que desarrollaba software para empresas que vendían seguros. Detectó que había mucho código muy similar entre las aplicaciones de cada cliente. Lo que hicieron fue refactorizarlo para dejar las partes comunes en las clases correspondientes y extraer lo concreto de las necesidades de cada cliente a una clase distinta. De esta forma, invirtieron más tiempo al principio del desarrollo ya que tuvieron que pensar y planificar esas partes comunes pero posteriormente, cuando tenían que añadir un nuevo cliente era mucho más rápido y sencillo ya que solo había que implementar unas pocas clases específicas.
‘Empaquetar’ operaciones relacionadas.
En aquellos casos en los que no se pueda esconder la información, compartir datos o planificar para ser más flexibles, se podría organizar el código de forma que compartiera operaciones en grupos similares, por ejemplo: funciones estadísticas, manipulación de cadenas, manipulación de bits, gráficos..etc. Aquí las clases tendrían el significado de agrupar dichas operaciones relacionadas.
Conseguir una refactorizción específica.
Muchos de los refactorings más conocidos conllevan la creación de una nueva clase por ejemplo incluir la conversión de una clase en dos, esconder una delegación, introducir una extensión para una clase…etc.

Vistas todas estas sugerencias por parte del autor, queda claro que crear una clase no es algo tan trivial y arbitrario como pudiera parecer, y por eso, insisto, en la dificultad que entraña si se quiere hacer de la mejor forma posible el software que desarrollemos. Y es que, no solo hay que conocer muy bien el dominio del problema que se intenta resolver sino que además hay que saber nombrar y crear las clases justas, necesarias, bien definidas y aisladas que resuelven dicho problema para crear buen código.

Pues eso, intentaremos seguir mejorando.

Chimpún.