El patrón factory method define una interfaz para la creación de un objeto, pero dejando en manos de las subclases la decisión de qué clase concreta instanciar.
Permite que una clase delegue en sus subclases las instanciaciones.

El Producto declara la interfaz, que es común a todos los objetos que puede producir la clase creadora y sus subclases.
Los Productos Concretos son distintas implementaciones de la interfaz de producto.
La clase Creadora declara el método fábrica que devuelve nuevos objetos de producto. Es importante que el tipo de retorno de este método coincida con la interfaz de producto.
Los Creadores Concretos sobrescriben el Factory Method base, de modo que devuelva un tipo diferente de producto.
JuegoLaberintopublic class JuegoLaberinto {
JuegoLaberinto() {};
// factory methods:
Laberinto makeLaberinto() { return new Laberinto(); }
Sala makeSala(int numSala) { return new Sala(numSala); }
Pared makePared() { return new Pared(); }
Puerta makePuerta(Sala sala1, Sala sala2) {
return new Puerta(sala1, sala2);
}
Laberinto crearLaberinto () { ... }
}
Laberinto crearLaberinto () {
Laberinto miLab = makeLaberinto();
Sala hab1 = makeSala(1);
Sala hab2 = makeSala(2);
Puerta unaPuerta = makePuerta(hab1, hab2);
miLab.agregarSala(hab1);
miLab.agregarSala(hab2);
hab1.setLado(Direccion.NORTE, makePared());
hab1.setLado(Direccion.ESTE, unaPuerta);
hab1.setLado(Direccion.SUR, makePared());
hab1.setLado(Direccion.OESTE, makePared());
hab2.setLado(Direccion.NORTE, makePared());
hab2.setLado(Direccion.ESTE, makePared());
hab2.setLado(Direccion.SUR, makePared());
hab2.setLado(Direccion.OESTE, unaPuerta);
return miLab;
}
public class JuegoLaberintoMinado extends JuegoLaberinto {
Pared makePared() {
return new ParedMinada();
}
Sala makeSala(int numSala) {
return new SalaMinada(numSala);
}
}
public class JuegoLaberintoHechizado extends JuegoLaberinto {
Sala makeSala(int numSala) {
return new SalaHechizada(numSala, lanzarHechizo());
}
Puerta makePuerta(Sala sala1, Sala sala2) {
return new PuertaHechizada(sala1, sala2);
}
private Hechizo lanzarHechizo() { ... }
}


Ventajas:
Desventajas:





Class adapter:
Object adapter:


Ventajas:
Desventajas:


EnhancedWriter originalEnhancedWriter ampliado – herencia fuera de controlEnhancedWriter ampliado – herencia fuera de controlVentajas:
Desventajas:
El decorator cambia la piel, el strategy cambia las tripas


Sin distinguir entre Observable y Subject:
Publisher = Observable = SubjectConcreteSubscriber SubjectCon Subject separado:
Subscriber = ObserverObservable y SubjectObserver.update()
ConcreteObserver mantiene referencia a ConcreteSubjectConcreteSubject.getState() (pull)update(context) para pasar la información necesaria al suscriptorupdate(this) para que el suscriptor extraiga la información necesaria pidiéndosela al publicadorConcreteSubscriber.setPublisher() para vincularlos permanentemente (opción menos flexible)

Ventajas:
accept() depende del tipo de Visitor y del tipo de ElementDesventajas:
Element visitados deben ser estables

class Document {
var state: String = _
var expirationDate: Date = _
// ...
def publish(currentUser: User): Unit = state match {
case "draft" =>
if (currentUser.role == "admin") {
state = "published"
} else {
state = "moderation"
}
case "moderation" =>
if (currentUser.role == "admin") {
state = "published"
}
case "published" =>
if (new Date().after(expirationDate)) {
state = "draft"
}
case _ =>
// Handle any unexpected state.
throw new IllegalStateException(s"Invalid document state: $state")
}
}

El objeto de dominio mantiene el estado e incorpora la lógica de persistencia sobre ese estado.
save(), delete(), etc.)Desventajas:
El patrón Data Access Object se usa para abstraer y encapsular los accesos a las fuentes de datos, proporcionando una capa de persistencia con independencia del soporte concreto de almacenamiento (BD relacional, NoSQL, ficheros, etc.).
Problemas (sin DAO):
// Interfaz DAO - define el contrato
public interface UsuarioDAO {
void create(Usuario usuario);
Usuario read(int id);
void update(Usuario usuario);
void delete(int id);
List<Usuario> findAll();
}
// Implementación JDBC
public class UsuarioDAOImpl implements UsuarioDAO {
private Connection connection;
public UsuarioDAOImpl(Connection connection) {
this.connection = connection;
}
@Override
public void create(Usuario usuario) {
String sql =
"INSERT INTO usuarios (nombre, email) VALUES (?, ?)";
try (PreparedStatement stmt =
connection.prepareStatement(sql)) {
stmt.setString(1, usuario.getNombre());
stmt.setString(2, usuario.getEmail());
stmt.executeUpdate();
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
...
...
@Override
public Usuario read(int id) {
String sql =
"SELECT * FROM usuarios WHERE id = ?";
try (PreparedStatement stmt =
connection.prepareStatement(sql)) {
stmt.setInt(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return new Usuario(
rs.getInt("id"),
rs.getString("nombre"),
rs.getString("email")
);
}
}
} catch (SQLException e) {
throw new PersistenceException(e);
}
return null;
}
// ... otros métodos (update, delete, findAll)
}
Ventajas:
Desventajas:
Evolución moderna del DAO que surge con la popularidad de frameworks como Spring Data JPA. Ofrece una abstracción de más alto nivel que el DAO tradicional, proporcionando operaciones CRUD genéricas sin necesidad de implementación manual.
Relación Repository
// Interfaz Repository - hereda CRUD automático
@Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Long> {
// Métodos derivados (queries generadas automáticamente)
Optional<Usuario> findByEmail(String email);
List<Usuario> findByNombreContainingIgnoreCase(String nombre);
List<Usuario> findByCreatedAtAfter(LocalDateTime fecha);
boolean existsByEmail(String email);
}
// Entity con JPA
@Entity
@Table(name = "usuarios")
@Data
@NoArgsConstructor
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String nombre;
@Column(unique = true, nullable = false)
private String email;
@CreationTimestamp
private LocalDateTime createdAt;
}
// Usando el Repository en un Servicio
@Service
public class UsuarioService {
private final UsuarioRepository usuarioRepository;
@Autowired
public UsuarioService(UsuarioRepository usuarioRepository) {
this.usuarioRepository = usuarioRepository;
}
public Usuario crearUsuario(Usuario usuario) {
if (usuarioRepository.existsByEmail(usuario.getEmail())) {
throw new EmailYaExisteException();
}
return usuarioRepository.save(usuario);
}
public Usuario obtenerPorId(Long id) {
return usuarioRepository.findById(id)
.orElseThrow(() -> new UsuarioNoEncontradoException());
}
public List<Usuario> buscarPorNombre(String nombre) {
return usuarioRepository.
findByNombreContainingIgnoreCase(nombre);
}
}
CrudRepository@Transactional por defecto@DataJpaTestSirve para crear objetos planos o Plain Old Java Objects (POJO) que se envían entre aplicaciones, capas, o servidores remotos. Un DTO
Problema a resolver:
// Entity JPA (contiene datos + lógica de negocio)
@Entity
@Table(name = "usuarios")
@Data
@NoArgsConstructor
public class Usuario {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String nombre;
@Column(unique = true, nullable = false)
private String email;
@JsonIgnore // No serializar en JSON
private String passwordHash;
@CreationTimestamp
private LocalDateTime createdAt;
// Métodos de negocio
public boolean validarPassword(String rawPassword) {
return BCrypt.checkpw(rawPassword, this.passwordHash);
}
}
// DTO - solo datos públicos (sin contraseña)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UsuarioDTO {
private Long id;
private String nombre;
private String email;
private LocalDateTime createdAt;
}
// Mapper usando ModelMapper (spring-boot-starter-modelmapper)
@Component
public class UsuarioMapper {
private final ModelMapper modelMapper;
@Autowired
public UsuarioMapper(ModelMapper modelMapper) {
this.modelMapper = modelMapper;
}
public UsuarioDTO toDTO(Usuario usuario) {
return modelMapper.map(usuario, UsuarioDTO.class);
}
public Usuario toEntity(UsuarioDTO usuarioDTO) {
return modelMapper.map(usuarioDTO, Usuario.class);
}
public List<UsuarioDTO> toDTOList(List<Usuario> usuarios) {
return usuarios.stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
}
// Controlador REST usando DTO
@RestController
@RequestMapping("/api/usuarios")
public class UsuarioController {
private final UsuarioService usuarioService;
private final UsuarioMapper usuarioMapper;
@GetMapping("/{id}")
public ResponseEntity<UsuarioDTO>
obtenerUsuario(@PathVariable Long id) {
Usuario usuario = usuarioService.obtenerPorId(id);
return ResponseEntity.ok(usuarioMapper.toDTO(usuario));
}
@GetMapping
public ResponseEntity<List<UsuarioDTO>>
listarUsuarios() {
List<Usuario> usuarios = usuarioService.listarTodos();
return ResponseEntity.ok(usuarioMapper.toDTOList(usuarios));
}
@PostMapping
public ResponseEntity<UsuarioDTO>
crearUsuario(@RequestBody UsuarioDTO usuarioDTO) {
Usuario usuario = usuarioMapper.toEntity(usuarioDTO);
Usuario creado = usuarioService.crearUsuario(usuario);
return ResponseEntity.created(URI.create("/api/usuarios/" +
creado.getId()))
.body(usuarioMapper.toDTO(creado));
}
}
Ventajas:
Desventajas:
@Service
public class UsuarioService {
private final UsuarioRepository usuarioRepository;
private final UsuarioMapper usuarioMapper;
@Autowired
public UsuarioService(UsuarioRepository usuarioRepository,
UsuarioMapper usuarioMapper) {
this.usuarioRepository = usuarioRepository;
this.usuarioMapper = usuarioMapper;
}
public UsuarioDTO obtenerPorId(Long id) {
Usuario usuario = usuarioRepository.findById(id)
.orElseThrow(() -> new UsuarioNoEncontradoException());
return usuarioMapper.toDTO(usuario); // Entity → DTO
}
public List<UsuarioDTO> buscarPorNombre(String nombre) {
List<Usuario> usuarios = usuarioRepository
.findByNombreContainingIgnoreCase(nombre);
return usuarioMapper.toDTOList(usuarios); // List<Entity> → List<DTO>
}
public UsuarioDTO crearUsuario(UsuarioDTO usuarioDTO) {
Usuario usuario = usuarioMapper.toEntity(usuarioDTO); // DTO → Entity
Usuario creado = usuarioRepository.save(usuario);
return usuarioMapper.toDTO(creado); // Entity → DTO
}
}