Plantillas y herencia
El patron de diseño "metodo plantilla" documentado en el libro "Patrones de diseño" (de Gamma y otros) propone abstraer los pasos canónicos de la operación en un método implementado en la clase abstracta - en nuestro caso step() - y redefinir los pasos variables en sus subclases. De esta forma, la herencia es el mecanismo principal para separar lo que es constante, de lo que varía. El método plantilla es parte del frozenspot mientras que las operaciones abstractas (o los ganchos) dan lugar a la implementación de los hotspots.
Note, en la siguiente figura, como las subclases de Robot redefinen las operaciones abstractas dando lugar a variantes de robot.
Tenemos dos tipos de Robots que pueden agregarse a nuestro Juego. Los robots con orugas, alimentados con energía nuclear y armados lasers (NuclearCaterpillarRobotWithLasers) y los robots robots con orugas, alimentados con energía nuclear y armados bombas (NuclearCaterpillarRobotWithBombs). Ambas clases de robots tienen el mismo comportamiento de locomoción (orugas) y relativo a su fuente de energía (nuclear) . Dicho comportamiento común se ha abstraído en la clase abstracta NuclearCaterpillarRobot. En las subclases concretas quedan la implementaciones del método fireArms(), dado que difieren una de otra.
La subclasificación es un mecanismo sencillo para especializar el comportamiento de nuestros robots. Las subclases no solo puede redefinir el comportamiento heredado sino que tienen acceso a todas las variables de instancia del robot (las heredadas, si las hubieras y las definidas en la subclase).
Pero note en la siguiente imagen lo que sucede a medida que aparecen nuevas alternativas para cada hotspot y si se intentan combinar arbitrariamente (cosa que sería deseable).
En este diseño, se ha subclasificado la clase Robot con el objetivo de obtener cuatro clases de robots.
fuente de energía | locomoción | armamento | |
NuclearOvercraftRobotWithLasers | nuclear | colchón de aire | lasers |
NuclearOvercraftRobotWithBombs | nuclear | colchón de aire | bombas |
NuclearCaterpillarRobotWithLasers | nuclear | orugas | lasers |
NuclearCaterpillarRobotWithBombs | nuclear | orugas | bombas |
Todas ellas son idénticas en lo que se refiere a fuente de energía (nuclear). La clase abstracta NuclearRobot abstrae ese comportamiento común, que queda encapsulado en el método consumeBattery() heredado por las 4 clases concretas de robot. Luego, dos de las clases de robots implementan locomoción por orugas (caterpillar). Este comportamiento común se encapsula en el método move() de la clase abstracta NuclearCaterpilarRobot. Las otras dos clases implementan locomoción por colchón de aire (overcraft). Ese comportamiento común se encapsula en el método move() de la clase abstracta NuclearOvercraftRobot.
Observe que el comportamiento de armamento se encapsula en el método fireArms(). Necesitamos que una de las subclases de NuclearCaterpillarRobot implemente fireArms() para obtener comportamiento de armamento con lasers (NuclearCaterpillarRobotWithLasers). Un método fireArms() idéntico se requiere en una de las subclases de NuclearOvercraftRobot. Algo similar ocurre con el método fireArms() correspondiente a la implementación de armamento con bombas. No hay forma de abstraer ese comportamiento en una clase abstracta (para evitar duplicar código) sin reformular toda la jerarquía y que ocurra algo similar con otro de los comportamientos variables.
La explosión en el numero de subclases y la duplicación de código evidencian las limitaciones de la herencia como estrategia para separar plantillas y ganchos. En situaciones como estas puede explorarse la composición como una estrategia alternativa.
Obra publicada con Licencia Creative Commons Reconocimiento Compartir igual 4.0