Programación Avanzada 2.º BT TI · DGETP / UTU
Profe Macón
Programación Avanzada
2.º BT · Tecnologías de la Información
PM
Tienda de Cómics · Unidad de persistencia · Material 05

Un contrato común:
interfaces y nuestros DAO

Ya sabemos para qué existe ProductoDAO. Ahora descubriremos cómo expresar operaciones comunes, qué significa implementar una interfaz y por qué el servicio puede depender de un contrato.

CET1 · POOCET2 · Interfaces y polimorfismoCET3 · DAO y persistenciaJava 17
1 · Tienda de Cómics

Dos DAO: un problema que ya conocemos

En el material anterior creamos ProductoDAO para que el servicio no tuviera que escribir consultas SQL. Ahora imaginemos que nuestra tienda comienza a registrar clientes. Necesitamos también ClienteDAO.

Ambos necesitan guardar, listar, modificar y eliminar, pero trabajan con distintos objetos y distintas tablas. ¿Qué parte se repite? ¿Y cuál necesariamente será diferente?

Piensa antes de leer: ¿podemos establecer las operaciones que debe ofrecer cada DAO sin escribir una única implementación de SQL para todos?
2 · Tienda de Cómics

Recuperamos la diferencia entre clase e interfaz

Una clase puede definir atributos, constructores y métodos con comportamiento. Una interfaz permite establecer un contrato: qué operaciones debe ofrecer una clase que decida implementarla.

En nuestro caso el contrato dirá que un DAO permite guardar, listar, modificar y eliminar. No dirá cuál es la tabla, qué SQL utiliza ni cómo abre la conexión. Eso seguirá siendo responsabilidad del DAO concreto.

Una interfaz no es una clase que «hace varias cosas automáticamente». Una clase puede implementar varias interfaces, pero eso no equivale a heredar la implementación completa de varias clases.

3 · Tienda de Cómics

Primero, un contrato sin genéricos

Vamos a comenzar con un ejemplo que utiliza solamente los tipos que ya conocemos:

Contrato específico: ejemplo de introducción
public interface ProductoPersistencia {
    void guardar(Producto producto) throws SQLException;
    List<Producto> listar() throws SQLException;
    void modificar(Producto producto) throws SQLException;
    void eliminar(long codigo) throws SQLException;
}

Observa que hay cabeceras de métodos terminadas en punto y coma. En este contrato no escribimos los bloques con { } que ejecutan SQL.

throws SQLException comunica que estas operaciones pueden propagar un error JDBC. No significa que la interfaz se conecte ni que gestione por sí sola la excepción.

Idea clave: el contrato describe capacidades; una implementación proporciona el comportamiento.
4 · Tienda de Cómics

¿Cómo se compromete ProductoDAO con ese contrato?

La palabra reservada implements conecta una clase con la interfaz que se compromete a implementar.

public class ProductoDAO implements ProductoPersistencia {

    @Override
    public void guardar(Producto producto) throws SQLException {
        // Aquí va nuestro INSERT con PreparedStatement.
    }

    @Override
    public List<Producto> listar() throws SQLException {
        // Aquí va nuestro SELECT y recorrido de ResultSet.
        return new ArrayList<Producto>();
    }

    @Override
    public void modificar(Producto producto) throws SQLException {
        // Aquí va nuestro UPDATE.
    }

    @Override
    public void eliminar(long codigo) throws SQLException {
        // Aquí va nuestro DELETE.
    }
}

Los cuerpos son marcadores didácticos, no la versión funcional del DAO. El SQL real es el que estudiamos en el SCORM 04.

@Override indica que el método implementa una operación declarada en la interfaz. Si escribimos mal el nombre o una firma, el compilador puede advertirnos.

La interfaz exige esos métodos, pero no decide cómo los implementamos.

5 · Tienda de Cómics

Una variable puede tener el tipo de la interfaz

Hasta ahora utilizábamos una referencia directa a una clase concreta:

ProductoDAO dao = new ProductoDAO();

También podemos escribir:

ProductoPersistencia dao = new ProductoDAO();

¿Qué cambió? El objeto sigue siendo un ProductoDAO. Lo que cambia es el tipo de la variable mediante la cual lo utilizamos.

Desde esa variable podemos invocar los métodos previstos en el contrato:

Producto producto = new Producto(101, "Batman: Año Uno", 790, 8);
dao.guardar(producto);
List<Producto> productos = dao.listar();

Este es un primer ejemplo de polimorfismo: trabajamos mediante el tipo de una interfaz y Java ejecuta la implementación del objeto concreto.

6 · Tienda de Cómics

¿Y si queremos utilizar el mismo contrato para ClienteDAO?

La interfaz anterior menciona Producto en sus métodos. No podemos utilizarla sin cambios para guardar Cliente.

En lugar de copiar una interfaz por cada entidad, podemos utilizar un parámetro de tipo. Lo representaremos con la letra T.

import java.sql.SQLException;
import java.util.List;

public interface CrudDAO<T> {
    void guardar(T objeto) throws SQLException;
    List<T> listar() throws SQLException;
    void modificar(T objeto) throws SQLException;
    void eliminar(long codigo) throws SQLException;
}

Esto se llama una interfaz genérica. La letra T no es una clase llamada T: representa un tipo que se indicará cuando usemos la interfaz.

Usamos long para el identificador porque en este primer ejemplo ambos DAO compartirían ese tipo de código. Más adelante veremos cómo generalizar también el identificador.

7 · Tienda de Cómics

¿Qué representa T en nuestra tienda?

Cuando escribimos:

public class ProductoDAO implements CrudDAO<Producto> {
    // Implementaciones con Producto y su tabla.
}

Estamos indicando que, para este DAO, T representa Producto. Por tanto, la operación guardar(T objeto) será implementada como guardar(Producto producto).

Podemos imaginar otro DAO:

public class ClienteDAO implements CrudDAO<Cliente> {
    // Implementaciones con Cliente y su tabla.
}

Este segundo ejemplo es una posibilidad futura: no presupone que ya hayamos creado la clase Cliente. Tampoco significa que los dos DAO ejecuten el mismo SQL.

Observación: reutilizamos el contrato, no el código concreto para guardar o consultar.
8 · Tienda de Cómics

¿Qué ventaja tiene para ProductoService?

En lugar de declarar un atributo del tipo concreto ProductoDAO, el servicio puede depender de la interfaz:

public class ProductoService {
    private CrudDAO<Producto> productoDAO;

    public ProductoService(CrudDAO<Producto> productoDAO) {
        this.productoDAO = productoDAO;
    }

    // Los métodos del servicio aplican las reglas del negocio
    // y luego solicitan guardar(), listar(), etc.
}

Esto se conoce como recibir una dependencia por el constructor. El servicio necesita las operaciones del contrato, pero no decide por sí solo cuál es la implementación concreta.

En Main, que coordina la creación de los componentes, podríamos preparar la implementación:

CrudDAO<Producto> dao = new ProductoDAO();
ProductoService service = new ProductoService(dao);

Conservamos nuestra idea original: Main construye y conecta componentes; ProductoController recibe eventos; ProductoService aplica las reglas; el DAO realiza persistencia.

Importante: este ejemplo muestra el cambio de dependencia. No reemplaza todavía el código completo de ProductoService, con sus validaciones y excepciones propias.
9 · Tienda de Cómics

¿Todas las clases DAO deben tener cuatro métodos?

No. La interfaz debe representar un contrato que realmente necesitemos.

Si un DAO solamente permite consultar una tabla de referencia, obligarlo a implementar eliminar() puede ser una mala decisión. Las interfaces deben diseñarse para las necesidades del programa, no para completar una lista por costumbre.

También debemos recordar que las interfaces no resuelven por sí solas los errores SQL ni aseguran la persistencia: solamente establecen operaciones disponibles. La implementación concreta sigue siendo responsable del JDBC, el cierre de recursos y el acceso a datos.

10 · Tienda de Cómics

Comprueba lo aprendido

Selecciona una respuesta y revisa la devolución.

1. ¿Cuál es el papel principal de CrudDAO?

2. En «CrudDAO<Producto> dao = new ProductoDAO();», ¿qué objeto construimos?

3. ¿Qué significa T en CrudDAO<T>?

4. ¿Qué ventaja tiene declarar CrudDAO<Producto> dentro de ProductoService?

5. ¿Debemos obligar a todos los DAO a implementar CRUD completo?

11 · Tienda de Cómics

Documentamos y dejamos abierta una pregunta

Microentrega de documentación

  1. Explica con tus palabras la diferencia entre el contrato de una interfaz y su implementación en un DAO.
  2. En el código CrudDAO<Producto>, identifica qué significa el parámetro de tipo.
  3. Dibuja un esquema con ProductoService, CrudDAO<Producto> y ProductoDAO, y escribe una responsabilidad para cada elemento.
  4. Explica qué ventaja obtendríamos si pudiéramos cambiar el DAO de MySQL por otro que conserve temporalmente los productos en una lista.
Tres niveles: mínimo viable: reconocer interface, implements y @Override; estándar: explicar y aplicar CrudDAO<Producto>; desafío: proponer cómo recibir distintos DAO por el constructor y anticipar un caso en el que convenga separar contratos.

La próxima pregunta: si ProductoService recibe una interfaz, ¿podemos cambiar la forma de guardar productos sin modificar sus reglas de negocio? Esa será una excelente oportunidad para profundizar el polimorfismo y el bajo acoplamiento con dos implementaciones reales.

Material en curso