Ejercicios de Patrones de diseño

Builder

Computer

  1. Crea un Builder para la siguiente clase Computer:

    1. Los campos HDD y RAM son obligatorios, y se deben pasar en el constructor del Builder.

    2. Los campos isGraphicsCardEnabled y isBluetoothEnabled son opcionales y se deben establecer con métodos setter del Builder

    3. Crea un constructor privado en la clase Computer para impedir que se instancien objetos de esta clase, si no es a través del Builder.

  2. Crea un programa principal que instancia un Computer usando el Builder.

class Computer { // required String HDD; String RAM; // optional boolean isGraphicsCardEnabled; boolean isBluetoothEnabled; }

TextBox

Añade un Builder a la siguiente clase TextBox.

El método build() debe asegurar que:

class TextBox { String texto; int ancho, alto; @Override public String toString() { return ancho + "x" + alto + "\n" + "┏" + "━".repeat(ancho) + "┓\n" + ("┃" + " ".repeat(ancho) + "┃\n").repeat((alto - 1) / 2) + (alto > 0 ? "┃" + " ".repeat((ancho - texto.length() + 1) / 2) + texto + " ".repeat((ancho - texto.length()) / 2) + "┃\n" : "") + ("┃" + " ".repeat(ancho) + "┃\n").repeat(alto / 2) + "┗" + "━".repeat(ancho) + "┛\n"; } }

AlertDialog

  1. Crea un clase AlertDialog con los siguientes campos:

    String title, text, yesButtonText, noButtonText;
  2. Añade un Builder a la classe AlertDialog:

    • Los campos title y text son obligatorios.

    • Los campos yesButtonText y noButtonText son opcionales.

  3. Añade los siguientes métdodos al Builder:

    Builder setYes() // establece el yesButtonText a "Ok" Builder setNo() // establece el yesButtonText a "Cancel"
  4. Añade el siguiente método al Builder:

    void show(){ AlertDialog alertDialog = build(); JPanel jPanel = new JPanel(); jPanel.setLayout(new BoxLayout(jPanel, BoxLayout.PAGE_AXIS)); jPanel.add(new JLabel(alertDialog.text)); if (alertDialog.yesButtonText != null) jPanel.add(new JButton(alertDialog.yesButtonText)); if (alertDialog.noButtonText != null) jPanel.add(new JButton(alertDialog.noButtonText)); JFrame jFrame = new JFrame(title); jFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); jFrame.add(jPanel); jFrame.pack(); jFrame.setLocationRelativeTo(null); jFrame.setVisible(true); }
  5. Comprueba que funciona el AlertDialog:

    new AlertDialog.Builder("Suscribirse", "Desea suscribirse a mi canal?") .setYes("Sí, suscribeme ahora mismo") .setNo("No, otro día") .show();

Callback

Calculadora

Haz que la siguiente clase Calculadora devuelva resultados o errores con un Callback:

class Calculadora { static int suma(int... numeros) { int resultado = 0; for (Integer numero : numeros) resultado += numero; // ¿ y si resultado + numero > Integer.MAX_VALUE ? return resultado; } static int divide(int a, int b) { return a/b; // ¿ y si b es 0 ? } } public class Main { public static void main(String[] args) { int resultadoSuma = Calculadora.suma(1, 2, 3, 10, 20); System.out.println("La suma es: " + resultadoSuma); int resultadoDivision = Calculadora.divide(13, 2); System.out.println("La division es: " + resultadoDivision); int resultadoOtraSuma = Calculadora.suma(1, 2147483646, 1); System.out.println("La suma es: " + resultadoOtraSuma); int resultadoOtraDivision = Calculadora.divide(13, 0); System.out.println("La division es: " + resultadoOtraDivision); } }

SimuladorHipoteca

Haz que la siguiente clase SimuladorHipoteca retorne resultados o errores con un Callback:

import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; class SimuladorHipoteca { static double calcularCuota(double capital, double plazo) { // if (capital <= 0) error: capital no valido // if (plazo <= 0) error: plazo no valido double interes = 0; try { // obtener 'interes' del banco... interes = Double.parseDouble(HttpClient.newHttpClient().send(HttpRequest.newBuilder() .uri(URI.create("https://fakebank-tan.vercel.app/api/get-interest")) .GET().build(), HttpResponse.BodyHandlers.ofString()).body()); } catch (Exception e) { // error obteniendo interes } return capital*interes/12/(1-Math.pow(1+(interes/12),-plazo*12)); } } public class Main { public static void main(String[] args) { double cuota = SimuladorHipoteca.calcularCuota(2000, 2); System.out.println(cuota); } }

FileDownloader

La siguiente clase FileDownloader simula una utilidad para realizar descargas.

Haz que vaya notificando el progreso de descarga mediante un callback:

class FileDownloader { public void downloadFile(String filePath) { // simular progreso de descarga for (int percentage = 0; percentage <= 100; percentage += 10) { // actualizar el progresso cada 10% System.out.println("Loading progress: " + percentage + "%"); try { Thread.sleep(1000); } catch (InterruptedException ignored) {} } } } public class Main { public static void main(String[] args) { FileDownloader fileDownloader = new FileDownloader(); fileDownloader.downloadFile("http://.../myFile.txt"); } }

Factory

ProductFactory

Crea una clase Factory para los siguientes productos.

En el programa principal, sustiuye las llamadas a los constructores por llamadas al método factory.

abstract class Product { protected String title; protected double price; public Product(String title, double price) { this.title = title; this.price = price; } public abstract void display(); } class Book extends Product { protected String author; public Book(String title, double price, String author) { super(title, price); this.author = author; } @Override public void display() { System.out.println("Book - Title: " + title + ", Author: " + author + ", Price: " + price); } } class Electronic extends Product { protected String manufacturer; public Electronic(String title, double price, String manufacturer) { super(title, price); this.manufacturer = manufacturer; } @Override public void display() { System.out.println("Electronic - Title: " + title + ", Manufacturer: " + manufacturer + ", Price: " + price); } } class Clothing extends Product { protected String size; public Clothing(String title, double price, String size) { super(title, price); this.size = size; } @Override public void display() { System.out.println("Clothing - Title: " + title + ", Size: " + size + ", Price: " + price); } } public class Main { public static void main(String[] args) { Product book = new Book("Harry Potter and the Philosopher's Stone", 9.99, "J.K. Rowling"); book.display(); Product electronic = new Electronic("iPhone 12 Pro", 999.99, "Apple"); electronic.display(); Product clothing = new Clothing("Maxi Dress", 38, "ASOS"); clothing.display(); } }

GameCharacter

Crea una classe Factory llamada CharacterFactory para los siguientes personajes de un juego:

abstract class Character { protected String name; protected int health; protected int speed; public Character(String name, int health, int speed) { this.name = name; this.health = health; this.speed = speed; } public abstract void move(); public abstract void attack(); public abstract void die(); } class Knight extends Character { public Knight(String name, int health, int speed) { super(name, health, speed); } @Override public void move() { System.out.println("Knight is moving..."); } @Override public void attack() { System.out.println("Knight is attacking..."); } @Override public void die() { System.out.println("Knight died..."); } } class Wizard extends Character { public Wizard(String name, int health, int speed) { super(name, health, speed); } @Override public void move() { System.out.println("Wizard is moving..."); } @Override public void attack() { System.out.println("Wizard is attacking..."); } @Override public void die() { System.out.println("Wizard died..."); } } public class Main { public static void main(String[] args) { CharacterFactory factory = new CharacterFactory(); // create a knight Character knight = factory.createCharacter("knight", "Arthur", 100, 5); knight.move(); knight.attack(); knight.die(); // create a wizard Character wizard = factory.createCharacter("wizard", "Gandalf", 80, 7); wizard.move(); wizard.attack(); wizard.die(); } }

Notification System

Se desea implementar un sistema de Notificaciones. El sistema debe poder enviar tres tipos de notificaciones: email, sms y push.

A continuación se muestra un diagrama de las classes a implementar:

--- title: Notification System --- classDiagram SMSNotification ..|> Notification EmailNotification ..|> Notification PushNotification ..|> Notification class Notification { +String destination +Notification(String destination) +abstract sendNotification(): void +factory(String destination): Notification } class SMSNotification{ +sendNotification(): void } class EmailNotification{ +sendNotification(): void } class PushNotification{ +sendNotification(): void }

Para facilitar su uso, se ha incluido un metodo factory en la clase Notification, que retorna un objeto de la clase correspondiente según el parámetro destination recibido:

Las clases que implementan el método sendNotification(), por el momento, no es necesario que realicen la acción de enviar la notificación; simplemente escriben por pantalla un mensaje.

A continuación se muestra un ejemplo de cómo se usaría la clase Notification:

public class Main { public static void main(String[] args) { Notification.factory("gerard@benigaslo.com").sendNotification(); Notification.factory("http://benigaslo.com/gerard").sendNotification(); Notification.factory("+34 678 789 890").sendNotification(); } }

La salida sería la siguiente:

SENDING Email to: gerard@benigaslo.com SENDING Push to: http://benigaslo.com/gerard SENDING SMS to: +34 678 789 890

Observer (aka Listener)

Guess event

Transforma el siguiente juego, para que los objetos confetti y scoreSaver sean notificados cuando se acierte el número.

import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Scanner; import java.util.concurrent.ThreadLocalRandom; class Game { Confetti confetti; ScoreSaver scoreSaver; public Game(Confetti confetti, ScoreSaver scoreSaver) { this.confetti = confetti; this.scoreSaver = scoreSaver; } void start(){ int guess = ThreadLocalRandom.current().nextInt(10); Scanner scanner = new Scanner(System.in); int tries = 1; while(true) { int num = scanner.nextInt(); if (num == guess) break; tries++; } confetti.onWin(tries); scoreSaver.onWin(tries); } } class Confetti { public void onWin(int tries) { System.out.println("\uD83C\uDF89".repeat(tries)); } } class ScoreSaver { public void onWin(int tries) { try { Files.writeString(Path.of("score.txt"), "intentos:" + tries); } catch (IOException ignored) {} } } public class Main { public static void main(String[] args) { Confetti confetti = new Confetti(); ScoreSaver scoreSaver = new ScoreSaver(); Game game = new Game(confetti, scoreSaver); game.start(); } }

Termostato

Un termostato abre o cierra un circuito electrico en función de la temperatura.

En el siguiente programa, el termómetro mide periódicamente la temperatura y la debe notificar a los termostatos.

Los termostatos abren el "aire acondicionado" cuando la temperatura supera el máximo que tienen definido.

import java.util.concurrent.ThreadLocalRandom; class Termometro { private float temperatura; void start(){ while (true) { temperatura = ThreadLocalRandom.current().nextFloat()*50; System.out.println("Temperatura: " + temperatura); // notificar a los observadores la temperatura try { Thread.sleep(1000); } catch (Exception ignored) { } } } } class Termostato { String lugar; float maximo; public Termostato(String lugar, float maximo) { this.lugar = lugar; this.maximo = maximo; } public void update(float temperatura) { if (temperatura > maximo) { abrir(); } else { cerrar(); } } void abrir(){ System.out.println("Termostato [" + lugar + "] abierto"); } void cerrar(){ System.out.println("Termostato [" + lugar + "] cerrado"); } } public class Main { public static void main(String[] args) { Termometro termometro = new Termometro(); Termostato t1 = new Termostato("cocina", 30); Termostato t2 = new Termostato("salon", 20); Termostato t3 = new Termostato("habitacion", 26); // añadir los termostatos como observadores del termometro termometro.start(); } }

CircleButton

Implementa el patrón Lístener en la clase CircleButton para el evento de "hacer click con el ratón". Añade un listener a cada botón.

import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.util.ArrayList; import java.util.List; class CircleButton { int x, y, radius; Color color; public CircleButton(int x, int y, int radius, Color color) { this.x = x; this.y = y; this.radius = radius; this.color = color; } Shape getShape() { return new Ellipse2D.Double(x - radius, y - radius, radius * 2, radius * 2); } double distanceTo(int x, int y) { return Math.sqrt(Math.pow(x - this.x, 2) + Math.pow(y - this.y, 2)); // hipotenusa } } class Window extends JFrame { List<CircleButton> circleButtonList = new ArrayList<>(); void add(CircleButton circleButton) { circleButtonList.add(circleButton); } void open() { setSize(400, 400); setVisible(true); addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent mouseEvent) { for (CircleButton circleButton : circleButtonList) { if (circleButton.distanceTo(mouseEvent.getX(), mouseEvent.getY()) < circleButton.radius) { // button clicked. Notify observers!!! } } } }); } @Override public void paint(Graphics g) { circleButtonList.forEach(circleButton -> { g.setColor(circleButton.color); ((Graphics2D) g).fill(circleButton.getShape()); }); } } public class Main extends Frame { public static void main(String[] args) { Window window = new Window(); CircleButton blueCircleButton = new CircleButton(240, 120, 60, Color.BLUE); CircleButton redCircleButton = new CircleButton(140, 300, 40, Color.RED); CircleButton greenCircleButton = new CircleButton(40, 150, 20, Color.GREEN); window.add(blueCircleButton); window.add(redCircleButton); window.add(greenCircleButton); window.open(); } }

Trading

El siguiente programa simula una operacion de trading. Cada segundo obtiene el precio: cuando baja de $15 lo ""notifica"", y cuando baja de $10 ""realiza"" la operación de compra.

Transforma el patrón Callback utilizado, en un patrón Listener.

Se deben añadir dos listeners distintos para las acciones de notificar y comprar. No se deben crear dos clases que implementen el interface, sino que directamente se deben crear dos clases anónimas. (Una clase anónima es cuando creamos un objeto con new directamente implementando un interface o extendiendo una clase).

import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; class Trading { interface Callback { void call(double price); } static void getPrice(Callback callback) { Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> { try { // obtener precio double price = Double.parseDouble(HttpClient.newHttpClient().send(HttpRequest.newBuilder().uri(URI.create("https://fakebank-tan.vercel.app/api/get-price")).GET().build(), HttpResponse.BodyHandlers.ofString()).body()); callback.call(price); } catch (Exception ignored) {} }, 0, 1, TimeUnit.SECONDS); } } public class Main { public static void main(String[] args) { Trading.getPrice(new Trading.Callback() { @Override public void call(double price) { if (price < 15) { System.out.println("ENVIAR NOTIFICACION:" + price); } if (price < 10) { System.out.println("COMPRAR:" + price); } } }); } }

Singleton

PrinterSpooler

Transforma la clase PrinterSpooler en un Singleton Synchronized:

import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class PrinterSpooler { int jobCount; PrinterSpooler() { jobCount = 0; } void printJob(String job) { System.out.println("Printing job " + (++jobCount) + ": " + job); } } public class Main { public static void main(String[] args) throws InterruptedException { PrinterSpooler printerSpooler = new PrinterSpooler(); List<Callable<Void>> printOrders = new ArrayList<>(); for (int i = 1; i <= 10; i++) { final int docId = i; printOrders.add(() -> { printerSpooler.printJob("Document" + docId); return null; }); } ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.invokeAll(printOrders); executorService.shutdown(); System.out.println("FINISHED. Jobs printed: " + printerSpooler.jobCount); } }

SensorLogger

Transforma la clase SensorLogger en un Singleton Lazy:

import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.time.LocalDateTime; import java.util.concurrent.ThreadLocalRandom; class SensorData { String sensorID; LocalDateTime timestamp; double value; public SensorData(String sensorID, LocalDateTime timestamp, double value) { this.sensorID = sensorID; this.timestamp = timestamp; this.value = value; } } class SensorLogger { private final String fileName; SensorLogger(String fileName) { this.fileName = fileName; } public void logData(SensorData data) { try (PrintWriter writer = new PrintWriter(new FileWriter(fileName, true))) { writer.println(data.sensorID + "," + data.timestamp + "," + data.value); } catch (IOException e) { e.printStackTrace(); } } } class Sensor { interface DataReceiver { void receive(SensorData sensorData); } void start(DataReceiver dataReceiver){ while(true) { dataReceiver.receive(new SensorData("SensorXYZ", LocalDateTime.now(), ThreadLocalRandom.current().nextDouble())); try { Thread.sleep(1000); } catch (InterruptedException ignored) { } } } } public class Main { public static void main(String[] args) { SensorLogger sensorLogger = new SensorLogger("data.log"); new Sensor().start(new Sensor.DataReceiver() { @Override public void receive(SensorData sensorData) { System.out.println("Saved " + sensorData.sensorID + "," + sensorData.timestamp + "," + sensorData.value); sensorLogger.logData(sensorData); } }); } }

GameManager

Transforma en un Singleton Synchornized la clase GameManager:

class GameManager { static int score; void incrementScore(int points) { int newScore = score + points; if (new Random().nextInt(10) >= 0) { score = newScore; } } } public class Main { public static void main(String[] args) throws InterruptedException { List<Callable<Void>> gameParts = new ArrayList<>(); for (int i = 0; i < 100; i++) { gameParts.add(() -> { new GameManager.incrementScore(1); return null; }); } ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.invokeAll(gameParts); executorService.shutdown(); System.out.println(GameManager.score); } }

SQLConnection

Transforma en un Singleton Synchronized la clase Database:

class Database { public static Connection getConnection() throws SQLException { return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "myuser", "mypassword"); } } public class Main { public static void main(String[] args) throws SQLException { Connection connection = Database.getConnection(); connection.createStatement().executeQuery("SELECT * FROM table"); } }

Adapter

Multiplier

Haz que el método multiplica() obtenga los dos números a multiplicar llamando a dos métodos de un Adaptador.

class Multiplicador { int multiplica(int num1, int num2){ return num1 * num2; } } public class Main { public static void main(String[] args) { Multiplicador multiplicador = new Multiplicador(); int resultado = multiplicador.multiplica(1000, 2000); System.out.println(resultado); } }

ListView

Haz que el método show() obtenga la cantidad y cada uno de los ítems mediante un Adaptador.

import java.util.List; class ListView { void show(List<String> items){ for (int i = 0; i < items.size(); i++) { System.out.println(items.get(i)); } } } public class Main { public static void main(String[] args) { List<String> items = List.of("Java","Python","Javascript","C#", "C++", "TypeScript", "PHP"); ListView listView = new ListView(); listView.show(items); } }

Pinger

Haz que el método ping obtenga las direcciones IP de un Adaptador.

import java.io.IOException; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; class Pinger { void ping(List<String> ips) { for (String ip : ips) { try { if (InetAddress.getByName(ip).isReachable(500)) { System.out.println(ip + " is online"); } else { System.out.println(ip + " is down"); } } catch (IOException ignored) {} } } } public class Main { public static void main(String[] args) { Pinger pinger = new Pinger(); List<String> ips = new ArrayList<>(); for (int i = 1; i < 254; i++) { ips.add("10.2.1." + i); } pinger.ping(ips); } }