Todos los lenguajes y plataformas actuales se basan en el paradigma de la programación orientada a objetos (OOP por sus siglas en inglés). Aunque a diario trabajamos con objetos, no todo el mundo comprende realmente lo que es el polimorfismo o para qué sirve una clase abstracta, por poner un ejemplo.
La potencia de la orientación a objetos lleva implícita mucha complejidad y una larga curva de aprendizaje. Lo que en unos casos es una buena manera de resolver un problema, en otros es la forma de hacer el código más frágil. Es decir, no siempre conviene crear una jerarquía de clases, dependiendo del caso puede ser más conveniente crear una asociación entre objetos que colaboran.
Desafortunadamente no hay reglas universales que sirvan para toda la gama de problemas que nos podamos encontrar pero hay ciertos principios y patrones que nos pueden dar pistas sobre cual es el diseño más conveniente en un momento dado. Con fines docentes se suele explicar la OOP mediante ejemplos relacionados con el mundo que conocemos: véase el típico ejemplo de la clase Animal
, de la que hereda la clase Mamífero
, de la que a su vez hereda la clase Cuadrúpedo
, de la que a su vez heredan las clases Perro
y Gato
.
El símil no está mal en sí mismo, lo que sucede es que las clases en el software no siempre casan directamente con los objetos del mundo real, porque el software difiere mucho de éste. Las clasificaciones naturales de los objetos, no tienen por qué ser clasificaciones adecuadas en el software.
La conceptualización y el modelado son una espada de doble filo, pues como vamos a mostrar, la realidad es demasiado compleja de modelar mediante OOP y el resultado puede ser un código muy acoplado, muy difícil de reutilizar.
Veamos el ejemplo de la clase Rectangulo
. El rectángulo tiene dos atributos, Ancho
y Alto
y un método que es capaz de calcular el área. Ahora necesitamos una clase Cuadrado
. En geometría el cuadrado es un rectángulo, por tanto, si copiamos esta clasificación diríamos que Cuadrado
hereda de Rectangulo
. Vale, entonces definimos Cuadrado
extendiendo de Rectangulo
.
Ahora damos la clase Cuadrado
a alguien que tiene que trabajar con ella y se encuentra con los atributos heredados, Ancho
y Alto
. Lo más probable es que se pregunte... ¿Qué significan el ancho y el alto en un cuadrado? Un cuadrado tiene todos sus lados iguales, no tiene ancho y alto. Este diseño no tienen sentido.
Para este caso particular, si lo que queremos es ahorrarnos reescribir el método que calcula el área, podemos crear ese método en una tercera clase que colabora con Rectangulo
y Cuadrado
para calcular el área. Así Rectangulo
sabe que cuando necesite calcular el área invocará al método de esta clase colaboradora pasando Ancho
y Alto
como parámetros y Cuadrado
pasará dos veces la longitud de uno de sus lados.
Una de las mejores formas que hay de ver si la API que estamos diseñando es intuitiva o no, es usarla. TDD propone usarla antes de implementarla, lo que le da in giro completo a la forma en que creamos nuestras clases. Puesto que todo lo hacemos con objetos, el beneficio de diseñar adecuadamente cambia radicalmente la calidad del software.