« En caso de duda, composición ( I ) | Inicio | En caso de duda, composición ( y III ) »

En caso de duda, composición ( II )

¿Qué pasa si nuestra jerarquía de clases es muy profunda o está muy extendida?. Pues que corremos un riesgo muy grande de tener problemas si cambia el interfaz de la superclase.

Seguimos teniendo a nuestros programadores y nuestros camioneros levantándose por las mañanas, yendo a trabajar, y volviendo a casa por las noches. Supongamos que en la clase base ( Persona ) hay un método como éste:

public funcion pagar( cant: Number ): Billete { return new Billete( cant ); }

De manera que, cuando cualquier persona tiene que pagar algo ( la comida, el autobús, etc ), lo hace entregando un billete de la cantidad que corresponda. Recordemos que tanto los programadores como los camioneros extienden de persona, por lo que ya habrán heredado ese método.

Por tanto, todas las clases que interactúen con las personas ( programadores o camioneros ), tendrán que estar preparados para poder recoger billetes como medio de pago.

Pero ¿qué ocurre si, por cualquier circunstancia ( necesidades del programa, órdenes del cliente,… ) necesitamos cambiar la forma de pago?. Es decir, ¿qué pasaría si ahora, en vez de pagar con billetes, las personas deben pagar con monedas, o con tarjeta de crédito?. Pues cualquier cambio en el interfaz de la clase base, por pequeño que sea, se propagará a las clases hijas, y puede dar lugar a efectos inesperados en cualquier parte de nuestro programa. Porque, por ejemplo, el autobús no se puede pagar con tarjeta de crédito ( aunque el restaurante sí ).

Por tanto, cualquier cambio, por pequeño que sea, en la interfaz de la clase base puede requerir que hagamos cambios en muchos otras partes de nuestra aplicación. Y eso hay que intentar evitarlo.

Veamos una forma de hacerlo ( como casi siempre, no la única ). Supongamos que delegamos la responsabilidad de realizar los pagos en otra clase, llamada PagosManager

class PagosManager { var saldo: Number; function PagosManager( ) { } public function init( saldoInicial: Number ) { this.saldo = saldoInicial; } public funcion pagar( cant: Number ): Billete { return new Billete( cant ); } }

Y tanto las clases Programador como Camionero podrían implementar los pagos de la siguiente forma:

class Programador extends Persona { var pagosManager: PagosManager = new PagosManager( ); function Programador( ) { this.pagosManager = new PagosManager( ); this.pagosManager.init( 1000 ); } public function pagar( ): Billete { return this.pagosManager.pagar( 100 ); } }

Y si necesitáramos que el Programador pagara con tarjeta de crédito, el único cambio a realizar sería en la propia implementación de la clase Programador:

public function pagar( ): Tarjeta { return new Tarjeta( this.pagosManager.pagar( 100 ) ); }

El ejemplo no es demasiado bueno ( ni completo ), pero espero que pueda servir para entender el concepto que intento transmitir.

Igualmente, si hemos utilizado la herencia para reutilizar código, nos podemos encontrar con el problema inverso: que necesitemos que alguna de las clases hijas cambie su perfil, para lo cual debamos cambiar el perfil de la clase base, con lo cual, inducimos cambios en el resto de clases hijas ( que pueden no ser bienvenidos por el compilador, o por el resto del programa ).

Veámoslo con un ejemplo. Si la clase base ( Persona ), implementa el siguiente método:

public function getDNI( ): String { return this.dni; }

Ese método estará presente en la implementación de Programador y Camionero. Pero ¿qué pasa si al programador le pide su jefe que le diga su DNI pero encriptado por un algoritmo md5? ( lo sé, el ejemplo es malo, pero los jefes son capaces de cualquier cosa ). Pues que, al ser ese método parte de la implementación heredada de la clase base, si cambiamos la implementación de ese método, cambiará también para el Camionero, que empezará a devolver su DNI encriptado. Obviamente, se puede sobrescribir el método sólo en la clase Programador, pero para eso no hacía falta heredarlo, ¿no?, porque entonces ya no es funcionalidad común, es interfaz común, y eso, como dice la palabra, se puede hacer que sea parte de un interfaz común para ambas clases hijas.

Y el tema todavía da para otro post