Profundizar en patrones creacionales: Builder Explorar patrones estructurales: Decorator y Adapter Analizar patrones de comportamiento: Observer y Strategy Caso práctico: Aplicación Swing con múltiples patrones
El patrón Builder nos permite construir objetos complejos paso a paso.
// Producto
class Pizza {
private String masa;
private String salsa;
private String queso;
private List<String> toppings = new ArrayList<>();
// Getters...
@Override
public String toString() {
return "Pizza con masa " + masa + ", salsa " + salsa +
", queso " + queso + " y toppings: " + toppings;
}
}
// Builder
class PizzaBuilder {
private Pizza pizza = new Pizza();
public PizzaBuilder setMasa(String masa) {
pizza.masa = masa;
return this;
}
public PizzaBuilder setSalsa(String salsa) {
pizza.salsa = salsa;
return this;
}
public PizzaBuilder setQueso(String queso) {
pizza.queso = queso;
return this;
}
public PizzaBuilder addTopping(String topping) {
pizza.toppings.add(topping);
return this;
}
public Pizza build() {
return pizza;
}
}
// Director (opcional)
class PizzaDirector {
public Pizza construirPizzaMargarita(PizzaBuilder builder) {
return builder
.setMasa("delgada")
.setSalsa("tomate")
.setQueso("mozzarella")
.addTopping("albahaca")
.build();
}
}
// Uso
public class Main {
public static void main(String[] args) {
PizzaBuilder builder = new PizzaBuilder();
// Construcción manual
Pizza pizzaPersonalizada = builder
.setMasa("gruesa")
.setSalsa("BBQ")
.setQueso("cheddar")
.addTopping("jamón")
.addTopping("piña")
.build();
// Usando director
PizzaDirector director = new PizzaDirector();
Pizza pizzaMargarita = director.construirPizzaMargarita(new PizzaBuilder());
}
}
// Producto (interface)
interface Pizza {
String getDescription();
}
// Implementaciones concretas
class PizzaMargarita implements Pizza {
private String masa;
private String salsa;
private String queso;
private List<String> toppings = new ArrayList<>();
public PizzaMargarita() {
this.masa = "delgada";
this.salsa = "tomate";
this.queso = "mozzarella";
this.toppings.add("albahaca");
}
@Override
public String getDescription() {
return "Pizza Margarita con masa " + masa + ", salsa " + salsa +
", queso " + queso + " y toppings: " + toppings;
}
}
class PizzaHawaiana implements Pizza {
private String masa;
private String salsa;
private String queso;
private List<String> toppings = new ArrayList<>();
public PizzaHawaiana() {
this.masa = "gruesa";
this.salsa = "BBQ";
this.queso = "cheddar";
this.toppings.add("jamón");
this.toppings.add("piña");
}
@Override
public String getDescription() {
return "Pizza Hawaiana con masa " + masa + ", salsa " + salsa +
", queso " + queso + " y toppings: " + toppings;
}
}
// Factory Method (interface)
interface PizzaFactory {
Pizza crearPizza();
}
// Implementaciones concretas de Factory
class PizzaMargaritaFactory implements PizzaFactory {
@Override
public Pizza crearPizza() {
return new PizzaMargarita();
}
}
class PizzaHawaianaFactory implements PizzaFactory {
@Override
public Pizza crearPizza() {
return new PizzaHawaiana();
}
}
// Uso
public class Main {
public static void main(String[] args) {
// Crear pizzas usando diferentes factories
PizzaFactory factoryMargarita = new PizzaMargaritaFactory();
Pizza pizzaMargarita = factoryMargarita.crearPizza();
System.out.println(pizzaMargarita.getDescription());
PizzaFactory factoryHawaiana = new PizzaHawaianaFactory();
Pizza pizzaHawaiana = factoryHawaiana.crearPizza();
System.out.println(pizzaHawaiana.getDescription());
}
}
// Product interfaces
interface Pizza {
String getDescription();
}
interface Bebida {
String getDescription();
}
// Pizza implementations
class PizzaMargaritaItaliana implements Pizza {
@Override
public String getDescription() {
return "Pizza Margarita Italiana con masa fina, salsa de tomate casera, mozzarella fresca y albahaca";
}
}
class PizzaHawaianaItaliana implements Pizza {
@Override
public String getDescription() {
return "Pizza Hawaiana Italiana con masa fina, salsa de tomate, mozzarella, jamón y piña";
}
}
class PizzaMargaritaAmericana implements Pizza {
@Override
public String getDescription() {
return "Pizza Margarita Americana con masa gruesa, salsa de tomate, queso mozzarella y orégano";
}
}
class PizzaHawaianaAmericana implements Pizza {
@Override
public String getDescription() {
return "Pizza Hawaiana Americana con masa gruesa, salsa BBQ, queso cheddar, jamón y piña";
}
}
// Bebida implementations
class BebidaItaliana implements Bebida {
@Override
public String getDescription() {
return "Vino tinto italiano";
}
}
class BebidaAmericana implements Bebida {
@Override
public String getDescription() {
return "Refresco de cola grande";
}
}
// Abstract Factory interface
interface PizzaRestaurantFactory {
Pizza crearPizza(String tipo);
Bebida crearBebida();
}
// Concrete Factories
class PizzeriaItaliana implements PizzaRestaurantFactory {
@Override
public Pizza crearPizza(String tipo) {
if (tipo.equals("margarita")) {
return new PizzaMargaritaItaliana();
} else if (tipo.equals("hawaiana")) {
return new PizzaHawaianaItaliana();
}
throw new IllegalArgumentException("Tipo de pizza no disponible");
}
@Override
public Bebida crearBebida() {
return new BebidaItaliana();
}
}
class PizzeriaAmericana implements PizzaRestaurantFactory {
@Override
public Pizza crearPizza(String tipo) {
if (tipo.equals("margarita")) {
return new PizzaMargaritaAmericana();
} else if (tipo.equals("hawaiana")) {
return new PizzaHawaianaAmericana();
}
throw new IllegalArgumentException("Tipo de pizza no disponible");
}
@Override
public Bebida crearBebida() {
return new BebidaAmericana();
}
}
// Client code
public class Main {
public static void main(String[] args) {
// Crear un pedido italiano
PizzaRestaurantFactory pizzeriaItaliana = new PizzeriaItaliana();
Pizza pizzaItalianaMargarita = pizzeriaItaliana.crearPizza("margarita");
Bebida bebidaItaliana = pizzeriaItaliana.crearBebida();
System.out.println("Pedido italiano:");
System.out.println(pizzaItalianaMargarita.getDescription());
System.out.println(bebidaItaliana.getDescription());
// Crear un pedido americano
PizzaRestaurantFactory pizzeriaAmericana = new PizzeriaAmericana();
Pizza pizzaAmericanaHawaiana = pizzeriaAmericana.crearPizza("hawaiana");
Bebida bebidaAmericana = pizzeriaAmericana.crearBebida();
System.out.println("\nPedido americano:");
System.out.println(pizzaAmericanaHawaiana.getDescription());
System.out.println(bebidaAmericana.getDescription());
}
}
Permite añadir funcionalidades a objetos existentes dinámicamente.
// Componente base
interface Coffee {
String getDescription();
double getCost();
}
// Componente concreto
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Café simple";
}
public double getCost() {
return 1.0;
}
}
// Decorator base
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public String getDescription() {
return decoratedCoffee.getDescription();
}
public double getCost() {
return decoratedCoffee.getCost();
}
}
// Decoradores concretos
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return decoratedCoffee.getDescription() + ", con leche";
}
public double getCost() {
return decoratedCoffee.getCost() + 0.5;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return decoratedCoffee.getDescription() + ", con azúcar";
}
public double getCost() {
return decoratedCoffee.getCost() + 0.2;
}
}
// Uso
public class CoffeeShop {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println("Descripción: " + coffee.getDescription());
System.out.println("Costo: $" + coffee.getCost());
}
}
Permite que interfaces incompatibles trabajen juntas.
// Interface objetivo
interface MediaPlayer {
void play(String fileName);
}
// Interface incompatible
interface AdvancedMediaPlayer {
void playMp4(String fileName);
void playAvi(String fileName);
}
// Implementación de la interface incompatible
class AdvancedMediaPlayerImpl implements AdvancedMediaPlayer {
public void playMp4(String fileName) {
System.out.println("Reproduciendo MP4: " + fileName);
}
public void playAvi(String fileName) {
System.out.println("Reproduciendo AVI: " + fileName);
}
}
// Adapter
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedPlayer;
public MediaAdapter() {
this.advancedPlayer = new AdvancedMediaPlayerImpl();
}
public void play(String fileName) {
if(fileName.endsWith(".mp4")) {
advancedPlayer.playMp4(fileName);
} else if(fileName.endsWith(".avi")) {
advancedPlayer.playAvi(fileName);
} else {
System.out.println("Formato no soportado");
}
}
}
// Cliente que usa el adapter
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
public AudioPlayer() {
this.mediaAdapter = new MediaAdapter();
}
public void play(String fileName) {
if(fileName.endsWith(".mp3")) {
System.out.println("Reproduciendo MP3: " + fileName);
} else {
mediaAdapter.play(fileName);
}
}
}
// Uso
public class MusicApp {
public static void main(String[] args) {
AudioPlayer player = new AudioPlayer();
player.play("cancion.mp3"); // Reproducción nativa
player.play("video.mp4"); // Usa adapter
player.play("pelicula.avi"); // Usa adapter
}
}
Permite definir una familia de algoritmos intercambiables.
// Strategy interface
interface SortStrategy {
void sort(int[] array);
}
// Estrategias concretas
class BubbleSort implements SortStrategy {
public void sort(int[] array) {
System.out.println("Ordenando con Bubble Sort");
// Implementación del bubble sort
}
}
class QuickSort implements SortStrategy {
public void sort(int[] array) {
System.out.println("Ordenando con Quick Sort");
// Implementación del quick sort
}
}
class MergeSort implements SortStrategy {
public void sort(int[] array) {
System.out.println("Ordenando con Merge Sort");
// Implementación del merge sort
}
}
// Contexto
class Sorter {
private SortStrategy strategy;
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void sort(int[] array) {
if(strategy == null) {
throw new IllegalStateException("Estrategia no definida");
}
strategy.sort(array);
}
}
// Uso
public class SortingApp {
public static void main(String[] args) {
int[] array = {64, 34, 25, 12, 22, 11, 90};
Sorter sorter = new Sorter();
// Usando diferentes estrategias
sorter.setStrategy(new BubbleSort());
sorter.sort(array); // Ordenamiento con bubble sort
sorter.setStrategy(new QuickSort());
sorter.sort(array); // Ordenamiento con quick sort
}
}
// Interface Observable (Subject)
interface WeatherStation {
void registerObserver(WeatherObserver observer);
void removeObserver(WeatherObserver observer);
void notifyObservers();
}
// Interface Observer
interface WeatherObserver {
void update(float temperature, float humidity, float pressure);
}
// Implementación Concreta del Observable
class WeatherData implements WeatherStation {
private List<WeatherObserver> observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
public void registerObserver(WeatherObserver observer) {
observers.add(observer);
}
public void removeObserver(WeatherObserver observer) {
observers.remove(observer);
}
public void notifyObservers() {
for(WeatherObserver observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
// Implementaciones Concretas de Observers
class CurrentConditionsDisplay implements WeatherObserver {
public void update(float temperature, float humidity, float pressure) {
System.out.println("Condiciones actuales:");
System.out.println("Temperatura: " + temperature + "°C");
System.out.println("Humedad: " + humidity + "%");
}
}
class StatisticsDisplay implements WeatherObserver {
private List<Float> temperatures = new ArrayList<>();
public void update(float temperature, float humidity, float pressure) {
temperatures.add(temperature);
displayStats();
}
private void displayStats() {
float avg = temperatures.stream()
.reduce(0f, Float::sum) / temperatures.size();
float max = Collections.max(temperatures);
float min = Collections.min(temperatures);
System.out.println("\nEstadísticas de Temperatura:");
System.out.println("Promedio: " + avg + "°C");
System.out.println("Máxima: " + max + "°C");
System.out.println("Mínima: " + min + "°C");
}
}
// Uso
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
StatisticsDisplay statsDisplay = new StatisticsDisplay();
weatherData.registerObserver(currentDisplay);
weatherData.registerObserver(statsDisplay);
// Simulando cambios en el clima
weatherData.setMeasurements(25.5f, 65.0f, 1013.1f);
weatherData.setMeasurements(26.7f, 70.0f, 1014.2f);
}
}
Implementaremos un convertidor de unidades usando Swing y varios patrones de diseño.
// Strategy para diferentes tipos de conversión
interface ConversionStrategy {
double convert(double value);
String getFromUnit();
String getToUnit();
}
class CelsiusToFahrenheitStrategy implements ConversionStrategy {
public double convert(double celsius) {
return (celsius * 9/5) + 32;
}
public String getFromUnit() { return "Celsius"; }
public String getToUnit() { return "Fahrenheit"; }
}
class KilometersToMilesStrategy implements ConversionStrategy {
public double convert(double km) {
return km * 0.621371;
}
public String getFromUnit() { return "Kilómetros"; }
public String getToUnit() { return "Millas"; }
}
// Observer para actualizar la interfaz
interface ConversionObserver {
void onConversionPerformed(double result);
}
// La ventana principal implementa el observer
public class UnitConverterGUI extends JFrame implements ConversionObserver {
private JTextField inputField;
private JLabel resultLabel;
private JComboBox<String> conversionType;
private Map<String, ConversionStrategy> strategies;
public UnitConverterGUI() {
setupStrategies();
setupGUI();
}
private void setupStrategies() {
strategies = new HashMap<>();
strategies.put("Temperatura", new CelsiusToFahrenheitStrategy());
strategies.put("Distancia", new KilometersToMilesStrategy());
}
private void setupGUI() {
setTitle("Convertidor de Unidades");
setLayout(new GridLayout(4, 1, 10, 10));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
conversionType = new JComboBox<>(strategies.keySet().toArray(new String[0]));
inputField = new JTextField();
JButton convertButton = new JButton("Convertir");
resultLabel = new JLabel("Resultado: ", SwingConstants.CENTER);
convertButton.addActionListener(e -> performConversion());
add(conversionType);
add(inputField);
add(convertButton);
add(resultLabel);
pack();
setSize(300, 200);
setLocationRelativeTo(null);
}
private void performConversion() {
try {
String selected = (String) conversionType.getSelectedItem();
ConversionStrategy strategy = strategies.get(selected);
double value = Double.parseDouble(inputField.getText());
double result = strategy.convert(value);
onConversionPerformed(result);
} catch (NumberFormatException ex) {
resultLabel.setText("Error: Ingrese un número válido");
}
}
@Override
public void onConversionPerformed(double result) {
String selected = (String) conversionType.getSelectedItem();
ConversionStrategy strategy = strategies.get(selected);
resultLabel.setText(String.format("%.2f %s = %.2f %s",
Double.parseDouble(inputField.getText()),
strategy.getFromUnit(),
result,
strategy.getToUnit()));
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new UnitConverterGUI().setVisible(true);
});
}
}