La refactorización es el proceso de modificar el código de un sistema sin cambiar su comportamiento externo, con el objetivo de mejorar su estructura interna. Los patrones de diseño son herramientas poderosas que pueden ayudar a realizar esta tarea de manera más eficiente y efectiva. En esta sección, exploraremos cómo utilizar patrones de diseño para refactorizar código, mejorando su mantenibilidad, flexibilidad y legibilidad.

Objetivos de la Refactorización

Antes de entrar en detalles sobre cómo aplicar patrones de diseño en la refactorización, es importante entender los objetivos principales de este proceso:

  1. Mejorar la legibilidad del código: Hacer que el código sea más fácil de entender para otros desarrolladores.
  2. Reducir la complejidad: Simplificar el código para que sea más fácil de mantener y extender.
  3. Eliminar duplicación: Reducir la redundancia en el código.
  4. Aumentar la reutilización: Facilitar la reutilización de componentes de software.
  5. Facilitar la prueba: Hacer que el código sea más fácil de probar.

Proceso de Refactorización

El proceso de refactorización utilizando patrones de diseño puede seguir los siguientes pasos:

  1. Identificar áreas problemáticas: Localizar partes del código que son difíciles de entender, mantener o extender.
  2. Seleccionar patrones de diseño adecuados: Elegir patrones que puedan resolver los problemas identificados.
  3. Aplicar los patrones de diseño: Refactorizar el código utilizando los patrones seleccionados.
  4. Probar el código refactorizado: Asegurarse de que el comportamiento del sistema no ha cambiado.

Ejemplos de Refactorización Usando Patrones de Diseño

Ejemplo 1: Refactorización con el Patrón Singleton

Problema: Supongamos que tenemos una clase DatabaseConnection que se instancia múltiples veces en diferentes partes del código, lo que puede llevar a problemas de rendimiento y consistencia.

Código Original:

public class DatabaseConnection {
    public DatabaseConnection() {
        // Código para establecer la conexión
    }
    
    public void query(String sql) {
        // Código para ejecutar la consulta
    }
}

// Uso en diferentes partes del código
DatabaseConnection db1 = new DatabaseConnection();
db1.query("SELECT * FROM users");

DatabaseConnection db2 = new DatabaseConnection();
db2.query("SELECT * FROM orders");

Refactorización con Singleton:

public class DatabaseConnection {
    private static DatabaseConnection instance;
    
    private DatabaseConnection() {
        // Código para establecer la conexión
    }
    
    public static DatabaseConnection getInstance() {
        if (instance == null) {
            instance = new DatabaseConnection();
        }
        return instance;
    }
    
    public void query(String sql) {
        // Código para ejecutar la consulta
    }
}

// Uso en diferentes partes del código
DatabaseConnection db = DatabaseConnection.getInstance();
db.query("SELECT * FROM users");

db = DatabaseConnection.getInstance();
db.query("SELECT * FROM orders");

Explicación:

  • Hemos convertido la clase DatabaseConnection en un Singleton, asegurando que solo haya una instancia de la conexión a la base de datos en todo el sistema.
  • Esto mejora el rendimiento y la consistencia al evitar múltiples conexiones innecesarias.

Ejemplo 2: Refactorización con el Patrón Factory Method

Problema: Supongamos que tenemos una clase Shape y varias subclases (Circle, Square, Triangle). El código actual crea instancias de estas clases directamente, lo que dificulta la extensión con nuevas formas.

Código Original:

public class ShapeFactory {
    public Shape createShape(String type) {
        if (type.equals("circle")) {
            return new Circle();
        } else if (type.equals("square")) {
            return new Square();
        } else if (type.equals("triangle")) {
            return new Triangle();
        }
        return null;
    }
}

// Uso en diferentes partes del código
ShapeFactory factory = new ShapeFactory();
Shape shape1 = factory.createShape("circle");
Shape shape2 = factory.createShape("square");

Refactorización con Factory Method:

public abstract class ShapeFactory {
    public abstract Shape createShape();
}

public class CircleFactory extends ShapeFactory {
    @Override
    public Shape createShape() {
        return new Circle();
    }
}

public class SquareFactory extends ShapeFactory {
    @Override
    public Shape createShape() {
        return new Square();
    }
}

public class TriangleFactory extends ShapeFactory {
    @Override
    public Shape createShape() {
        return new Triangle();
    }
}

// Uso en diferentes partes del código
ShapeFactory circleFactory = new CircleFactory();
Shape shape1 = circleFactory.createShape();

ShapeFactory squareFactory = new SquareFactory();
Shape shape2 = squareFactory.createShape();

Explicación:

  • Hemos utilizado el patrón Factory Method para crear instancias de Shape.
  • Esto facilita la extensión del código con nuevas formas sin modificar la lógica existente.

Ejercicio Práctico

Ejercicio: Refactoriza el siguiente código utilizando el patrón Decorator para añadir funcionalidad adicional a una clase TextEditor.

Código Original:

public class TextEditor {
    public void write(String text) {
        System.out.println(text);
    }
}

public class Main {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        editor.write("Hello, World!");
    }
}

Requisitos:

  1. Añadir funcionalidad para convertir el texto a mayúsculas antes de escribirlo.
  2. Añadir funcionalidad para añadir un prefijo "LOG: " antes de escribir el texto.

Solución:

public interface TextEditor {
    void write(String text);
}

public class SimpleTextEditor implements TextEditor {
    @Override
    public void write(String text) {
        System.out.println(text);
    }
}

public abstract class TextEditorDecorator implements TextEditor {
    protected TextEditor decoratedEditor;

    public TextEditorDecorator(TextEditor decoratedEditor) {
        this.decoratedEditor = decoratedEditor;
    }

    public void write(String text) {
        decoratedEditor.write(text);
    }
}

public class UpperCaseDecorator extends TextEditorDecorator {
    public UpperCaseDecorator(TextEditor decoratedEditor) {
        super(decoratedEditor);
    }

    @Override
    public void write(String text) {
        super.write(text.toUpperCase());
    }
}

public class LogDecorator extends TextEditorDecorator {
    public LogDecorator(TextEditor decoratedEditor) {
        super(decoratedEditor);
    }

    @Override
    public void write(String text) {
        super.write("LOG: " + text);
    }
}

public class Main {
    public static void main(String[] args) {
        TextEditor editor = new SimpleTextEditor();
        TextEditor upperCaseEditor = new UpperCaseDecorator(editor);
        TextEditor logEditor = new LogDecorator(upperCaseEditor);
        
        logEditor.write("Hello, World!");
    }
}

Explicación:

  • Hemos creado una interfaz TextEditor y una implementación básica SimpleTextEditor.
  • Hemos creado una clase abstracta TextEditorDecorator que implementa la interfaz TextEditor y contiene una referencia a un TextEditor decorado.
  • Hemos creado dos decoradores concretos: UpperCaseDecorator y LogDecorator, que añaden funcionalidad adicional antes de delegar la llamada al editor decorado.

Conclusión

La refactorización utilizando patrones de diseño es una técnica poderosa para mejorar la calidad del código. Al aplicar patrones de diseño, podemos hacer que el código sea más mantenible, flexible y fácil de entender. En esta sección, hemos visto cómo aplicar los patrones Singleton, Factory Method y Decorator para refactorizar código. Practicar estas técnicas te ayudará a convertirte en un desarrollador más eficiente y efectivo.

© Copyright 2024. Todos los derechos reservados