Antes de empezar vamos a ver que tipos de patrones tenemos, durante la materia solo vemos 3 tipos:
Patrones Creacionales
Estos patrones están relacionados con los mecanismos de creación de objetos, buscando adaptar el proceso de creación a diferentes situaciones. Los patrones creacionales ocultan los detalles de creación de objetos y ayudan a hacer el sistema independiente de cómo sus objetos son creados, compuestos y representados.Patrones Estructurales
Estos patrones se ocupan de cómo las clases y objetos son compuestos para formar estructuras más grandes. Los patrones estructurales ayudan a asegurar que si cambia una parte del sistema, el sistema completo no necesita cambiar. También son útiles para compartir funcionalidades de una manera que ofrece ventajas significativas.Patrones de Comportamiento
Estos patrones están relacionados con algoritmos y la asignación de responsabilidades entre objetos. Lo que describen es cómo interactúan y distribuyen la responsabilidad entre las clases y objetos.
Permite a interfaces incompatibles trabajar juntas. Su principal uso es hacer que el código existente funcione con otra interfaz, sin alterar el código original. Actúa como un puente entre estas interfaces.
Cliente
Es la parte que requiere un servicio.Ejemplo
Un programa de análisis de datos que requiere cargar datos. Supongamos que está diseñado para trabajar con datos en formato JSON, pero los datos disponibles están en formato XML.
Target
Es la interfaz en la que el cliente realiza las solicitudes.Ejemplo
En el caso de nuestro programa de análisis de datos,Target
podría ser una interfaz con un método comoloadData()
que está diseñado para aceptar datos en formato JSON.
Adapter
Traduce las solicitudes delTarget
en una forma que el Adaptee puede entender, haciendo cualquier adaptación necesaria.Ejemplo
Un adaptador de datos que implementaloadData()
donde, en lugar de requerir datos en formato JSON, convierte datos en formato XML a JSON y luego invoca el métodospecificRequest()
delAdaptee
para procesar los datos.
Adaptee
es la clase que tiene las funcionalidades que necesitamos, pero su interfaz no es compatible con lo que elClient
espera. Esta clase no sabe nada sobreTarget
y trabaja de manera independiente.
Client
public class MediaPlayer {
private Media media;
public MediaPlayer(Media media) {
this.media = media;
}
public String playMedia() {
return media.play();
}
public Media getMedia() {
return media;
}
public void setMedia(Media media) {
this.media = media;
}
}
Target
public interface Media {
public String play();
}
Adapter
public class VideoStreamAdapter implements Media {
private VideoStream adaptee;
public VideoStreamAdapter(VideoStream adaptee) {
this.adaptee = adaptee;
}
public String play() {
return adaptee.reproduce();
}
}
Adaptee
public class VideoStream {
public String reproduce() {
return "Directo.stream";
}
}
Hijos de Media
public class Audio implements Media {
public String play() {
return "musica.mp3";
}
}
public class VideoFile implements Media {
public String play() {
return "Video.mp4";
}
}
MediaPlayerTest
public class MediaPlayerTest {
Audio audio;
VideoFile video;
VideoStream stream;
VideoStreamAdapter adapter;
MediaPlayer client;
@BeforeEach
void setUp() throws Exception{
audio = new Audio();
video = new VideoFile();
stream = new VideoStream();
adapter = new VideoStreamAdapter(stream);
}
@Test
public void testClientDirecto() {
client = new MediaPlayer(adapter);
assertEquals("Directo.stream",client.playMedia());
}
@Test
public void testClientAudio() {
client = new MediaPlayer(audio);
assertEquals("musica.mp3",client.playMedia());
}
@Test
public void testClientVideo() {
client = new MediaPlayer(video);
assertEquals("Video.mp4",client.playMedia());
}
}
Define la estructura de un algoritmo en una operación, delegando algunos pasos a las subclases. Permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar su estructura general.
Este patron no tiene mucho secreto, en el primer objeto se define el esqueleto del algoritmo y en los objetos hijos se implementan los pasos.
AbstractClass
public abstract class Empleado {
private int cantidadHijos;
private boolean casado;
public Empleado(int cantidadHijos, boolean casado) {
this.cantidadHijos = cantidadHijos;
this.casado = casado;
}
public double sueldo() {
return this.sueldoBasico() + this.sueldoAdicional() - this.descuento();
}
public double descuento(){
return this.sueldoBasico() * 0.13 + this.sueldoAdicional() * 0.5;
}
public int getCantidadHijos() {
return this.cantidadHijos;
}
public boolean isCasado() {
return this.casado;
}
public abstract double sueldoBasico();
public abstract double sueldoAdicional();
}
ConcreteClass1
public class Pasante extends Empleado {
private int cantidadExamen;
public Pasante(int cantidadHijos, boolean casado, int cantidadExamen) {
super(cantidadHijos, casado);
this.cantidadExamen = cantidadExamen;
}
public double sueldoBasico() {
return 20000;
}
public double sueldoAdicional() {
return this.cantidadExamen * 2000;
}
}
ConcreteClass2
public class Planta extends Empleado{
private int aniosAntiguedad;
public Planta(int cantidadHijos, boolean casado, int aniosAntiguedad) {
super(cantidadHijos, casado);
this.aniosAntiguedad = aniosAntiguedad;
}
public double sueldoBasico() {
return 50000;
}
public double sueldoAdicional() {
double sum = this.getCantidadHijos() * 2000 + this.aniosAntiguedad * 2000;
return this.isCasado()? sum + 5000 : sum;
}
}
ConcreteClass3
public class Temporario extends Empleado{
private int cantidadHoras;
public Temporario(int cantidadHijos, boolean casado, int cantidadHoras) {
super(cantidadHijos, casado);
this.cantidadHoras = cantidadHoras;
}
public double sueldoBasico() {
return 20000 + this.cantidadHoras * 300 ;
}
public double sueldoAdicional() {
double sum = this.getCantidadHijos() * 2000;
return this.isCasado()? sum + 5000 : sum;
}
}
Test
public class MediaPlayerTest {
Empleado pasante;
Empleado planta;
Empleado temporario;
@BeforeEach
void setUp() throws Exception{
pasante = new Pasante(10, false, 10);
planta = new Planta(10, true, 10);
temporario = new Temporario(10, false, 10);
}
@Test
public void testSueldos() {
assertEquals(27400.0,pasante.sueldo());
assertEquals(66000.0,planta.sueldo());
assertEquals(30010.0,temporario.sueldo());
}
}
Este patrón es utilizado principalmente para organizar objetos en estructuras de árbol que representan jerarquías parte-todo. Permite a los clientes tratar objetos individuales y composiciones de objetos de manera uniforme.
Component
Es la interfaz o clase abstracta que define las operaciones comunes para tanto los objetos simples (Leaf) como los compuestos (Composite). Actúa como la clase base para todos los objetos dentro de la estructura.Operation():
Debe ser implementado por todos los objetos concretos, tanto hojas como compuestos.Add(Component):
Agrega subcomponentes, utilizado principalmente en los Composite.Remove(Component):
Remueve subcomponentes, utilizado principalmente en los Composite.GetChild(int):
Obtiene un subcomponente específico, utilizado principalmente en los Composite.
Leaf
Son los bloques de construcción básicos de la estructura, donde se implementan las operaciones más concretas sin delegar a otros objetos.Composite
Implementa métodos para manejar sus hijos y también implementa la operación que se aplica a cada uno de sus hijos.Operation()
: Implementa el método realizando una operación que generalmente implica iterar sobre sus hijos y llamando a su método Operation().Add(Component)
,Remove(Component)
,GetChild(int)
: Estos métodos están implementados para manipular y acceder a los subcomponentes.
Component
public abstract class FileSystem{
private String nombre;
private LocalDate fecha;
public FileSystem(String nombre, LocalDate fecha) {
this.nombre = nombre;
this.fecha = fecha;
}
public String getNombre() {
return this.nombre;
}
public LocalDate getFecha() {
return this.fecha;
}
public abstract int tamanoTotalOcupado();
public abstract Archivo archivoMasGrande();
public abstract Archivo archivoMasNuevo();
}
Leaf
public class Archivo extends FileSystem{
private int tamano;
public Archivo(String nombre, LocalDate fecha, int tamano) {
super(nombre, fecha);
this.tamano = tamano;
}
public Archivo archivoMasGrande() {
return this;
}
public Archivo archivoMasNuevo() {
return this;
}
public int tamanoTotalOcupado() {
return this.tamano;
}
}
Composite
public class Directorio extends FileSystem {
private List<FileSystem> files;
public Directorio(String nombre, LocalDate fecha) {
super(nombre, fecha);
this.files = new ArrayList<>();
}
public void agregar(FileSystem archivo) {
this.files.add(archivo);
}
public int tamanoTotalOcupado() {
return (
this.files.stream()
.mapToInt(file -> file.tamanoTotalOcupado())
.sum()
) + 32;
}
public Archivo archivoMasGrande() {
return this.files.stream()
.map(file -> file.archivoMasGrande())
.max((a1,a2) -> Integer.compare(a1.tamanoTotalOcupado(),a2.tamanoTotalOcupado()))
.orElse(null);
}
public Archivo archivoMasNuevo() {
return this.files.stream()
.map(file -> file.archivoMasNuevo())
.max((a1,a2) -> a1.getFecha().compareTo(a2.getFecha()))
.orElse(null);
}
}
ClientTest
public class MediaPlayerTest {
Archivo archivoChico, archivoGrande;
Directorio directorio, directorioCompuesto;
@BeforeEach
void setUp() throws Exception{
archivoChico = new Archivo("notas.txt", LocalDate.of(2000, 2, 20), 10);
archivoGrande = new Archivo("apuntes.txt", LocalDate.of(2010, 2, 20), 50);
directorio = new Directorio("Carpeta1", LocalDate.now());
directorio.agregar(archivoChico);
directorio.agregar(archivoGrande);
directorioCompuesto = new Directorio("CarpetaCompuesta", LocalDate.now());
directorioCompuesto.agregar(directorio);
}
@Test
public void testEspacio() {
assertEquals((10 + 50 + 32), directorio.tamanoTotalOcupado());
assertEquals((10 + 50 + 32 + 32), directorioCompuesto.tamanoTotalOcupado());
assertEquals(archivoGrande, directorioCompuesto.archivoMasGrande());
assertEquals(archivoGrande, directorioCompuesto.archivoMasNuevo());
}
}
Define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Este patrón permite que el algoritmo varíe independientemente de los clientes que lo utilizan.
Context
Delega trabajo a la estrategia asociada, pero mantiene el control sobre cómo y cuándo se llama a las estrategias. Puede proporcionar datos adicionales necesarios para la ejecución de la estrategia.Ejemplo
Una aplicación de navegación que puede calcular rutas utilizando diferentes algoritmos de estrategia. El contexto sería el planificador de rutas que decide cuándo y cómo calcular la ruta dependiendo de la estrategia seleccionada por el usuario (la más rápida, la más corta, la más económica, etc.).
Strategy
Es una interfaz que define un método común para todas las estrategias concretas.Ejemplo
En el contexto de una aplicación de navegación, la interfazStrategy
podría definir un métodocalculateRoute()
, que será implementado de diferente manera por cada estrategia concreta.
ConcreteStrategyA
,ConcreteStrategyB
,ConcreteStrategyC
Son implementaciones específicas de la interfazStrategy
. Cada una proporciona un comportamiento concreto o un algoritmo específicoConctreteStrategyA
Podría ser una estrategia que calcula la ruta más rápida usando autopistas.ConctreteStrategyB
Podría calcular la ruta más corta, evitando autopistasConctreteStrategyC
Podría optar por la ruta que consume menos combustible.
Context
public class Decodificador {
private List<Pelicula> grilla;
private List<Pelicula> reproducidas;
private Sugerencia criterioSugerencia;
public Decodificador() {
this.grilla = new ArrayList<>();
this.reproducidas = new ArrayList<>();
this.criterioSugerencia = new SugerenciaNovedad();
}
public void agregarAGrilla(Pelicula pelicula) { this.grilla.add(pelicula); }
public void agregarReproducida(Pelicula pelicula) { this.reproducidas.add(pelicula);}
public void setCriterioSugerencia(Sugerencia sugerencia) { this.criterioSugerencia = sugerencia; }
public List<Pelicula> obtenerSugerencias() {
return this.criterioSugerencia.obtenerSugerencias(this);
}
public List<Pelicula> getGrilla() { return this.grilla; }
public List<Pelicula> getReproducidas() { return this.reproducidas; }
}
public class Pelicula {
private String titulo;
private Year anioEstreno;
private double puntaje;
private List<Pelicula> peliculasSimilares;
public Pelicula(String titulo, double puntaje, Year anioEstreno) {
this.titulo = titulo;
this.anioEstreno = anioEstreno;
this.puntaje = puntaje;
this.peliculasSimilares = new ArrayList<>();
}
public String getTitulo() { return titulo;}
public Year getAnioEstreno() { return anioEstreno;}
public double getPuntaje() { return puntaje;}
public void establecerSimilitud(Pelicula pelicula) {
if (!this.peliculasSimilares.contains(pelicula)) {
this.peliculasSimilares.add(pelicula);
pelicula.establecerSimilitud(this);
}
}
public List<Pelicula> getPeliculasSimilares(){ return this.peliculasSimilares;}
}
Strategy
public abstract class Sugerencia {
public List<Pelicula> obtenerSugerencias(Decodificador decodificador){
return this.sugerirPeliculas(decodificador).stream()
.filter(pelicula -> !decodificador.getReproducidas().contains(pelicula))
.limit(3).collect(Collectors.toList());
}
public abstract List<Pelicula> sugerirPeliculas(Decodificador decodificador);
}
ConcreteStrategyA
public class SugerenciaNovedad extends Sugerencia {
public List<Pelicula> sugerirPeliculas(Decodificador decodificador) {
return decodificador.getGrilla().stream()
.sorted((p2,p1) -> p1.getAnioEstreno().compareTo(p2.getAnioEstreno()))
.collect(Collectors.toList());
}
}
ConcreteStrategyB
public class SugerenciaPuntaje extends Sugerencia {
public List<Pelicula> sugerirPeliculas(Decodificador decodificador) {
return decodificador.getGrilla().stream()
.sorted((p1,p2) -> Double.compare(p2.getPuntaje(), p1.getPuntaje()))
.collect(Collectors.toList());
}
}
ConcreteStrategyC
public class SugerenciaSimilaridad extends Sugerencia {
public List<Pelicula> sugerirPeliculas(Decodificador decodificador) {
return decodificador.getReproducidas().stream()
.map(pelicula -> pelicula.getPeliculasSimilares()).flatMap(lista -> lista.stream())
.distinct()
.collect(Collectors.toList());
}
}
DecodificadorTest
public class DecodificadorTest {
Decodificador decodificador;
Pelicula rocky1, rocky2, rocky3, rocky4, rocky5, terminator1, terminator2, terminator3;
Sugerencia novedad, puntaje, similaridad;
@BeforeEach
void setUp() throws Exception{
rocky1 = new Pelicula("Rocky 1", 10, Year.of(2000));
rocky2 = new Pelicula("Rocky 2", 9, Year.of(2001));
rocky3 = new Pelicula("Rocky 3", 8, Year.of(2002));
rocky4 = new Pelicula("Rocky 4", 7, Year.of(2003));
rocky5 = new Pelicula("Rocky 5", 6, Year.of(2004));
//Se podria establecer la similitud con cada pelicula pero es mucho
//Se hace con rocky2 porque busca las mimilares con las reproducidas
rocky2.establecerSimilitud(rocky1);
rocky2.establecerSimilitud(rocky3);
rocky2.establecerSimilitud(rocky4);
rocky2.establecerSimilitud(rocky5);
terminator1 = new Pelicula("Terminator1", 1, Year.of(2020));
terminator2 = new Pelicula("Terminator2", 2, Year.of(2021));
terminator3 = new Pelicula("Terminator3", 3, Year.of(2022));
decodificador = new Decodificador();
decodificador.agregarAGrilla(rocky1);
decodificador.agregarAGrilla(rocky2);
decodificador.agregarAGrilla(rocky3);
decodificador.agregarAGrilla(rocky4);
decodificador.agregarAGrilla(rocky5);
decodificador.agregarAGrilla(terminator1);
decodificador.agregarAGrilla(terminator2);
decodificador.agregarAGrilla(terminator3);
decodificador.agregarReproducida(rocky2);
decodificador.agregarReproducida(rocky4);
}
@Test
public void testSugerenciaNovedad() {
List<Pelicula> ultimas3 = new ArrayList<>();
ultimas3.add(terminator3);
ultimas3.add(terminator2);
ultimas3.add(terminator1);
assertEquals(ultimas3, decodificador.obtenerSugerencias());
}
@Test
public void testSugerenciaPuntaje() {
puntaje = new SugerenciaPuntaje();
decodificador.setCriterioSugerencia(puntaje);
List<Pelicula> masPuntaje = new ArrayList<>();
masPuntaje.add(rocky1);
masPuntaje.add(rocky3);
masPuntaje.add(rocky5);
assertEquals(masPuntaje, decodificador.obtenerSugerencias());
}
@Test
public void testSugerenciaSimilaridad() {
similaridad = new SugerenciaSimilaridad();
decodificador.setCriterioSugerencia(similaridad);
//Son las 3 similares sin reproducir
List<Pelicula> similaresRocky = new ArrayList<>();
similaresRocky.add(rocky1);
similaresRocky.add(rocky3);
similaresRocky.add(rocky5);
assertEquals(similaresRocky, decodificador.obtenerSugerencias());
}
}
Se utiliza para permitir a un objeto alterar su comportamiento cuando su estado interno cambia. El objeto parecerá cambiar su clase.
Context
Accede a la interfaz de State para realizar su comportamiento, que cambia dinámicamente según el estado actual.request()
Este método debería delegar la operación a la instancia actual de StateEjemplo
Imagina una aplicación de procesamiento de pedidos donde elContext
es unPedido
. ElPedido
puede tener varios estados comoPendiente
,Pagado
,Enviado
, yEntregado
. Cada uno de estos estados alterará cómo se procesan ciertas operaciones (por ejemplo, no se puede enviar un pedido antes de que esté pagado).
State
es una interfaz o una clase abstracta que define un métodohandle()
que todas las clases concretas de estado implementarán.Ejemplo
En el sistema de pedidos,State
tendría un métodohandle()
que podría ser llamado algo así comoprocesarSiguientePaso()
. Este método determinaría qué hacer a continuación con el pedido (por ejemplo, procesar el pago, enviar el pedido, etc.).
ConcreteStateA
,ConcreteStateB
Cada clase representa un estado específico delContext
y proporciona su propia implementación del métodohandle()
.ConcreteStateA
(PedidoPagado): Este estado podría manejar la lógica de preparar el pedido para el envío. La implementación dehandle()
en este estado podría cambiar el estado del pedido aEnviado
si todo está listo para el envío.ConcreteStateB
(PedidoEnviado): Este estado manejaría las acciones posteriores al envío, como notificar al cliente o cambiar el estado aEntregado
. La implementación dehandle()
aquí podría involucrar la verificación del progreso del envío y la actualización del estado del pedido.
Preguntar ¿Cuando el objeto state debe tener una instancia del context
En el patrón de diseño State, es común que los objetos de estado (State
) tengan acceso al objeto de contexto (Context
) para poder realizar cambios en el estado del contexto directamente. Sin embargo, si los objetos State
tienen una referencia directa al Context
o no depende del diseño específico y de los requisitos del sistema. Aquí te detallo los dos enfoques posibles:
- Descripción: En esta configuración, cada objeto
State
mantiene una referencia alContext
. Esto les permite no solo manejar su comportamiento específico sino también cambiar el estado delContext
directamente cuando se cumplan ciertas condiciones. - Ventajas:
- Control Directo: Los estados pueden controlar las transiciones a otros estados sin involucrar al
Context
, lo que simplifica el código delContext
. - Flexibilidad: Facilita la implementación de comportamientos complejos que dependen del estado y contexto actuales, como revertir a un estado anterior o saltar a estados no secuenciales.
- Control Directo: Los estados pueden controlar las transiciones a otros estados sin involucrar al
- Ejemplo: Un objeto
State
en un juego puede verificar si el jugador ha alcanzado ciertos puntos de logro y directamente cambiar el estado del juego para reflejar un nuevo nivel o modo de juego.
- Descripción: En esta configuración, los objetos
State
no mantienen una referencia directa alContext
. En su lugar, dependen de que elContext
pase de alguna forma cualquier información necesaria y maneje explícitamente los cambios de estado. - Ventajas:
- Desacoplamiento: Mayor desacoplamiento entre el estado y el contexto, lo que puede facilitar la prueba y mantenimiento de cada clase por separado.
- Reusabilidad: Los objetos
State
pueden ser más fácilmente reutilizables en diferentes contextos si no están fuertemente acoplados a una clase de contexto específica.
- Ejemplo: Un objeto
State
en una aplicación de procesamiento de documentos podría realizar operaciones como guardar o cargar archivos sin necesitar saber en qué estado específico de la UI se encuentra la aplicación.
Context
public class Excursion {
private String nombre;
private Estado estado;
private List<Usuario> inscriptos;
private List<Usuario> enEspera;
private LocalDate fechaInicio;
private LocalDate fechaFin;
private String puntoEncuentro;
private double costo;
private int cupoMinimo;
private int cupoMaximo;
public Excursion(String nombre, LocalDate fechaInicio, LocalDate fechaFin, String puntoEncuentro, double costo,
int cupoMinimo, int cupoMaximo) {
this.nombre = nombre;
this.estado = new Provisoria(this);
this.inscriptos = new ArrayList<>();
this.enEspera = new ArrayList<>();
this.fechaInicio = fechaInicio;
this.fechaFin = fechaFin;
this.puntoEncuentro = puntoEncuentro;
this.costo = costo;
this.cupoMinimo = cupoMinimo;
this.cupoMaximo = cupoMaximo;
}
public List<Usuario> getInscriptos() {
return inscriptos;
}
public List<Usuario> getEnEspera() {
return enEspera;
}
public Estado getEstado() {
return estado;
}
public void setEstado(Estado estado) {
this.estado = estado;
}
public int getCupoMinimo() {
return cupoMinimo;
}
public int getCupoMaximo() {
return cupoMaximo;
}
public void inscribir (Usuario unUsuario) {
this.estado.inscribir(unUsuario);
}
public boolean alcanzoMinimo() {
return this.getInscriptos().size() >= this.cupoMinimo;
}
public boolean alcanzoMaximo() {
return this.getInscriptos().size() >= this.cupoMaximo;
}
public String obtenerInformacion() {
return "La excursion '" + this.nombre
+ "' tiene un costo de " + this.costo
+ " con fecha de inicio " + this.fechaInicio.toString()
+ " y fecha de fin " + this.fechaFin.toString()
+ ".\nEl punto de encuentro es en '" + this.puntoEncuentro
+ "'. " + this.estado.obtenerInformacion();
}
public String getMailsInscriptos() {
return this.inscriptos.stream()
.map(inscripto -> inscripto.getMail())
.reduce("",(acumulator, element)-> acumulator +"\n" + element);
}
}
public class Usuario {
private String nombre;
private String apellido;
private String mail;
public Usuario(String nombre, String apellido, String mail) {
this.nombre = nombre;
this.apellido = apellido;
this.mail = mail;
}
public String getNombre() {
return nombre;
}
public String getApellido() {
return apellido;
}
public String getMail() {
return mail;
}
}
State
public abstract class Estado {
protected Excursion excursion;
public Estado(Excursion excursion) {
this.excursion = excursion;
}
public abstract void inscribir(Usuario unUsuario);
public abstract String obtenerInformacion();
}
ConcreteStateA
public class Provisoria extends Estado {
public Provisoria(Excursion excursion) {
super(excursion);
}
public void inscribir(Usuario unUsuario) {
if (!this.excursion.alcanzoMaximo()) {
this.excursion.getInscriptos().add(unUsuario);
if (this.excursion.alcanzoMinimo()) {
this.excursion.setEstado(new Definitiva(this.excursion));
}
}
}
public String obtenerInformacion() {
return "\nActualmente faltan " + (this.excursion.getCupoMinimo() - this.excursion.getInscriptos().size())
+ " personas para alcanzar el cupo mínimo.";
}
}
ConcreteStateB
public class Definitiva extends Estado {
public Definitiva(Excursion excursion) {
super(excursion);
}
public void inscribir(Usuario unUsuario) {
if (!this.excursion.alcanzoMaximo()) {
this.excursion.getInscriptos().add(unUsuario);
} else {
this.excursion.setEstado(new Completa(this.excursion));
this.excursion.inscribir(unUsuario);
}
}
public String obtenerInformacion() {
return "\nLa excursión está confirmada y tiene espacio para más inscripciones.";
}
}
ConcreteStateC
public class Completa extends Estado {
public Completa(Excursion excursion) {
super(excursion);
}
public void inscribir(Usuario unUsuario) {
this.excursion.getEnEspera().add(unUsuario);
}
public String obtenerInformacion() {
return "\nLa excursión está completa. Todos los nuevos inscriptos serán puestos en lista de espera.";
}
}
ExcursionTest
public class ExcursionTest {
Excursion excursion;
Usuario usuario;
@BeforeEach
void setUp() throws Exception{
usuario = new Usuario("Fabian", "Martinez", "fabian@gmail.com");
excursion = new Excursion("Viaje",
LocalDate.of(2000, 1, 1),
LocalDate.of(2000,2,1), "La Ciudad", 100, 3, 6);
}
@Test
public void testExcursion() {
excursion.inscribir(usuario);
excursion.inscribir(usuario);
excursion.inscribir(usuario);
assertEquals(3, excursion.getInscriptos().size());
excursion.inscribir(usuario);
excursion.inscribir(usuario);
excursion.inscribir(usuario);
assertEquals(6, excursion.getInscriptos().size());
excursion.inscribir(usuario);
assertEquals(1, excursion.getEnEspera().size());
}
}
Tengo que consultar porque tengo bajo acoplamiento, pero no se si esta bien
Permite añadir nuevas funcionalidades a objetos de manera dinámica, ofreciendo una alternativa flexible a la herencia para extender funcionalidades.
Component
Interfaz para objetos que pueden tener responsabilidades añadidas dinámicamente.Ejemplo
En una aplicación de renderizado de texto,Component
podría ser una interfazText
con un métododraw()
ConcreteComponent
Implementa la interfazComponent
, representando objetos a los que se añadirán responsabilidades.Ejemplo
PlainText
podría ser una clase que implementeText
, y su métododraw()
simplemente muestra el texto sin ningún formato.
Decorator
Es una clase abstracta que implementa la interfazComponent
y mantiene una referencia a un objetoComponent
.Ejemplo
TextDecorator
podría ser una clase abstracta que implementaText
y contiene un elementoText
que se decorará.
ConcreteDecoratorA
,ConcreteDecoratorB
Extienden la funcionalidad de Decorator añadiendo estado o comportamiento adicional.ConcreteDecoratorA
:BoldTextDecorator
podría ser una clase que decora un texto agregando negrita. Su métododraw()
llamarádraw()
delText
decorado y luego aplicará un estilo de negrita al texto.ConcreteDecoratorB
:ItalicTextDecorator
podría ser una clase que agrega funcionalidad para dibujar texto en cursiva.
Component
public interface FileAttributes {
default String prettyPrint() { return ""; }
default String getName() { return ""; }
default String getExtension() { return ""; }
default String getSize() { return ""; }
default String getDateCreated() { return ""; }
default String getDateModified() { return ""; }
default String getPermissions() { return ""; }
}
ConcreteComponent
public class File implements FileAttributes {
private String name;
private String extension;
private double size;
private LocalDate dateCreated;
private LocalDate dateModified;
private String permissions;
// Constructor para inicializar el archivo con todos los atributos necesarios
public File(String name, String extension, double size, LocalDate dateCreated, LocalDate dateModified, String permissions) {
this.name = name;
this.extension = extension;
this.size = size;
this.dateCreated = dateCreated;
this.dateModified = dateModified;
this.permissions = permissions;
}
public String prettyPrint() {
return "Archivo: " + getName() + " (." + getExtension() + "), Tamaño: " + getSize() + " MB\n" +
"Creado el: " + getDateCreated() + "\n" +
"Modificado el: " + getDateModified() + "\n" +
"Permisos: " + getPermissions();
}
public String getName() {
return name;
}
public String getExtension() {
return extension;
}
public String getSize() {
return String.format("%.2f", size);
}
public String getDateCreated() {
return dateCreated.toString();
}
public String getDateModified() {
return dateModified.toString();
}
public String getPermissions() {
return permissions;
}
}
Decorator
public abstract class Aspect implements FileAttributes {
protected FileAttributes component;
public Aspect(FileAttributes component) {
this.component = component;
}
public String prettyPrint() {
return component.prettyPrint();
}
}
ConcreteDecoratorA
public class AspectDateCreated extends Aspect {
public AspectDateCreated(FileAttributes component) {
super(component);
}
public String prettyPrint() {
return super.prettyPrint() + "Creado: " + getDateCreated() + "\n";
}
}
ConcreteDecoratorB
public class AspectDateModified extends Aspect {
public AspectDateModified(FileAttributes component) {
super(component);
}
@Override
public String prettyPrint() {
return super.prettyPrint() + "Modificado: " + getDateModified() + "\n";
}
}
ConcreteDecoratorC
public class AspectExtension extends Aspect {
public AspectExtension(FileAttributes component) {
super(component);
}
@Override
public String prettyPrint() {
return super.prettyPrint() + "Extensión: ." + getExtension() + "\n";
}
}
ConcreteDecoratorD
public class AspectName extends Aspect {
public AspectName(FileAttributes component) {
super(component);
}
@Override
public String prettyPrint() {
return super.prettyPrint() + "Nombre: " + getName() + "\n";
}
}
ConcreteDecoratorE
public class AspectPermissions extends Aspect {
public AspectPermissions(FileAttributes component) {
super(component);
}
@Override
public String prettyPrint() {
return super.prettyPrint() + "Permisos: " + getPermissions() + "\n";
}
}
ConcreteDecoratorF
public class AspectSize extends Aspect {
public AspectSize(FileAttributes component) {
super(component);
}
@Override
public String prettyPrint() {
return super.prettyPrint() + "Tamaño: " + getSize() + " MB\n";
}
}
Me rindo, despues pregunto en la clase.
Es utilizado para proporcionar un sustituto o marcador de posición para otro objeto. Controla el acceso a este objeto original, permitiendo realizar operaciones antes o después de pasar la llamada al objeto original.
- Subject:
- Descripción: Define la interfaz común para
RealSubject
yProxy
. Esta interfaz permite que un Proxy sea utilizado en lugar del objeto real. - Ejemplo: En un sistema de gestión de documentos,
Subject
podría ser una interfazDocument
con métodos comodisplay()
,edit()
ysave()
.
- Descripción: Define la interfaz común para
- RealSubject:
- Descripción: El objeto real que el proxy representa. Define el objeto real que contiene la lógica de negocio que debería ser controlada o mejorada por el proxy.
- Ejemplo:
DocumentImpl
podría ser una implementación concreta deDocument
, gestionando documentos en un sistema de archivos.
- Proxy:
- Descripción: Mantiene una referencia al
RealSubject
, controlando el acceso a este y pudiendo realizar tareas adicionales como cargar perezosa, control de acceso, logging, etc. - Función: Intercepta todas las llamadas dirigidas al
RealSubject
y puede realizar operaciones antes o después de pasar la llamada alRealSubject
. - Ejemplo:
DocumentProxy
podría ser un proxy que controla el acceso aDocumentImpl
. Antes de mostrar un documento, el proxy podría verificar si el usuario tiene los permisos necesarios.
- Descripción: Mantiene una referencia al
Subject
public interface DatabaseAccess {
Collection<String> getSearchResults(String queryString);
int insertNewRow(List<String> rowData);
}
RealSubject
public class DatabaseRealAccess implements DatabaseAccess {
private Map<String, List<String>> data;
private int currentId;
public DatabaseRealAccess() {
super();
this.data = new HashMap<>();
this.currentId = 3;
this.data.put("select * from comics where id=1", Arrays.asList("Spiderman", "Marvel"));
this.data.put("select * from comics where id=2", Arrays.asList("Batman", "DC comics"));
}
public Collection<String> getSearchResults(String queryString) {
return this.data.getOrDefault(queryString, Collections.emptyList());
}
public int insertNewRow(List<String> rowData) {
this.data.put("select * from comics where id=" + this.currentId, rowData);
this.currentId = this.currentId + 1;
return this.currentId - 1;
}
}
Proxy
public class DatabaseProxy implements DatabaseAccess{
private DatabaseAccess database;
private boolean isLog;
public DatabaseProxy (DatabaseAccess database) {
this.database = database;
this.isLog = false;
}
public void logIn () {
this.isLog = true;
}
public void closeSession() {
this.isLog = false;
}
public Collection<String> getSearchResults(String queryString) {
if (!this.isLog) {
throw new RuntimeException("access denied");
}
return this.database.getSearchResults(queryString);
}
public int insertNewRow(List<String> rowData) {
if (!this.isLog) {
throw new RuntimeException("access denied");
}
return this.database.insertNewRow(rowData);
}
}
ProxyTest
public class ProxyTest {
DatabaseAcess base;
DatabaseProxy proxy;
List<String> pelis_id1 = Arrays.asList("Spiderman", "Marvel");
List<String> pelis_id2 = Arrays.asList("Batman", "DC comics");
List<String> agregadas = Arrays.asList("Rocky", "Rambo");
@BeforeEach
void setUp() throws Exception{
base = new DatabaseRealAccess();
proxy = new DatabaseProxy(base);
}
@Test
public void testSinProxy() {
assertEquals(pelis_id1, base.getSearchResults("select * from comics where id=1"));
assertEquals(pelis_id2, base.getSearchResults("select * from comics where id=2"));
base.insertNewRow(agregadas);
assertEquals(agregadas, base.getSearchResults("select * from comics where id=3"));
}
@Test
public void testConProxy() {
Throwable exception = assertThrows(RuntimeException.class, () -> {
proxy.getSearchResults("select * from comics where id=1");
});
assertEquals("access denied", exception.getMessage());
proxy.logIn();
assertEquals(pelis_id1, base.getSearchResults("select * from comics where id=1"));
assertEquals(pelis_id2, base.getSearchResults("select * from comics where id=2"));
base.insertNewRow(agregadas);
assertEquals(agregadas, base.getSearchResults("select * from comics where id=3"));
}
}
Product
Es la interfaz o clase abstracta que define el tipo de objeto que el método fábrica creará.Ejemplo
En un software de gestión de documentos,Document
podría ser la interfazProduct
, con métodos comoopen()
,save()
, yclose()
ConcreteProduct
Es una implementación específica de la interfazProduct
, representando un producto específico creado por la fábrica concreta.Creator
ConcreteCreator