Como leía en un libro, asimilar patrones de diseño es como tener varios expertos sentados a tu lado mientras programas. Quize en esta ocasión agregar un experto más a mi lado comprendiendo el patrón "Decorator" (o Decorador en español). En especial llamó mi atención este patrón, porque leyendo un libro mencionaron que las operaciones en Java de I/O streaming utilizan este patrón por detrás para asignar funcionalidad en tiempo de ejecución.
File file = new File("c:/temp");
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
En muy resumidad palabras, el uso del patrón Decorator es más flexible que el uso de herencia, en el sentido de que la herencia asigna responsabilidad a clases en tiempo de compilación, mientras que el patrón Decorator las asigna en tiempo de ejecución ("runtime"). Como en casi todo patrón de diseño lo más sencillo es explicarlo directamente con código.
En el siguiente código de ejemplo vamos utilizar una aplicación hipotéctica de una academia de ingeniería de software. En dicha aplicación existe la entidad estudiante de ingeniería de software ("SoftwareEngineerStudent"), a la cual se le puede aplicar N decoradores para simular el proceso de aprendizaje durante su carrera. He aquí el patrón aplicado en UML:
El SoftwareEngineerStudent es representado con una interface donde el método que vamos a decorar en otras clases es el showCurriculum():
package patterns.decorator;
public interface SoftwareEngineerStudent {
/**
* Shows students curriculum.
*/
public void showCurriculum();
}
Seguidamente tenemos la clase que implementa nuestra anterior interface, el "SimpleSoftwareEngineerStudent". La única funcionalidad agregada es que imprimimos en consola que "Soy un estudiante de Ingeniería de Software":
package patterns.decorator;
public class SimpleSoftwareEngineer implements SoftwareEngineerStudent{
public void showCurriculum() {
System.out.println("I'm a software engineer student");
}
}
Ahora definimos nuestra clase decoradora base con el objetivo de permitir el poliformismo a la hora de entrenar (decorar) a los futuros estudiantes:
package patterns.decorator;
/**
* The Base decorator class.
*
*/
public abstract class SoftwareEngineerDecorator implements SoftwareEngineerStudent{
protected SoftwareEngineerStudent softwareEngineerStudent;
/**
* Constructor must receive an instance of SoftwareEngineerStudent so it can decorates
* something.
*/
public SoftwareEngineerDecorator(SoftwareEngineerStudent softwareEngineerStudent) {
this.softwareEngineerStudent = softwareEngineerStudent;
}
/**
* The operation that will be decorated by subclasses.
*/
public void showCurriculum() {
this.softwareEngineerStudent.showCurriculum();
}
}
Seguidamente creamos nuestras clases decoradoras extendiendo de la clase base SoftwareEngineerDecorator. Nótese que lo único que se hace es sobre escribir el método "showCurriculum()" para imprimir el conocimiento anterior junto con el nuevo (o decorado):
package patterns.decorator;
public class JavaProgrammingDecorator extends SoftwareEngineerDecorator {
public JavaProgrammingDecorator(
SoftwareEngineerStudent softwareEngineerStudent) {
super(softwareEngineerStudent);
}
public void showCurriculum() {
super.showCurriculum();
System.out.println("I have some basic knowledge in Java language.");
}
}
public class DesignPatternsDecorator extends SoftwareEngineerDecorator{
public DesignPatternsDecorator(
SoftwareEngineerStudent softwareEngineerStudent) {
super(softwareEngineerStudent);
}
public void showCurriculum() {
super.showCurriculum();
System.out.println("I know how to apply the most common design patterns");
}
}
public class DatabasesDecorator extends SoftwareEngineerDecorator {
public DatabasesDecorator(SoftwareEngineerStudent softwareEngineerStudent) {
super(softwareEngineerStudent);
}
public void showCurriculum() {
super.showCurriculum();
System.out.println("I can design ER databases.");
System.out.println("I know SQL.");
}
}
Por último tenemos la clase "SoftwareEngineerAcademy" para mostrar la aplicación de los decoradores. En el método principal se instancian dos estudiantes los cuales toman diferentes caminos en sus carreras. Los métodos trainIn...() toman la instancia del estudiante y le aplican un decorador que es retornado al final. Este mecanismo podría mejorarse aplicando un poco de reflection, pero vamos a dejarlo así para simplificar la explicación.
package patterns.decorator;
public class SoftwareEngineerAcademy {
public static SoftwareEngineerStudent trainInJava(
SoftwareEngineerStudent softwareEngineerStudent) {
SoftwareEngineerStudent studentTrainedInJava =
new JavaProgrammingDecorator(softwareEngineerStudent);
return studentTrainedInJava;
}
public static SoftwareEngineerStudent trainInDatabases(
SoftwareEngineerStudent softwareEngineerStudent) {
SoftwareEngineerStudent studentTrainedInDatabases =
new DatabasesDecorator(softwareEngineerStudent);
return studentTrainedInDatabases;
}
public static SoftwareEngineerStudent trainInDesignPatterns(
SoftwareEngineerStudent softwareEngineerStudent) {
SoftwareEngineerStudent studentTrainedInDesignPatterns =
new DesignPatternsDecorator(softwareEngineerStudent);
return studentTrainedInDesignPatterns;
}
public static void main(String[] args) {
SoftwareEngineerStudent studentA = new SimpleSoftwareEngineer();
studentA = SoftwareEngineerAcademy.trainInJava(studentA);
studentA = SoftwareEngineerAcademy.trainInDatabases(studentA);
System.out.println("**********************");
System.out.println("Student A Curriculum: ");
System.out.println("**********************");
studentA.showCurriculum();
SoftwareEngineerStudent studentB = new SimpleSoftwareEngineer();
studentB = SoftwareEngineerAcademy.trainInJava(studentB);
studentB = SoftwareEngineerAcademy.trainInDesignPatterns(studentB);
System.out.println("**********************");
System.out.println("Student B Curriculum: ");
System.out.println("**********************");
studentB.showCurriculum();
}
}
Cuando corremos el programa vemos que efectivamente cuando se aplicaron los decoradores, al final el estudiante muestra el curriculum correctamente:
**********************
Student A Curriculum:
**********************
I'm a software engineer student
I have some basic knowledge in Java language.
I can design ER databases.
I know SQL.
**********************
Student B Curriculum:
**********************
I'm a software engineer student
I have some basic knowledge in Java language.
I know how to apply the most common design patterns