Tipos de Datos

Clases

Clase

Mecanismo que
permite definir nuevos tipos de datos

Tipo de Dato

Permite definir qué operaciones son válidas

Tipo de Dato

También permite
definir invariantes

Invariantes

Condiciones que deben
ser ciertas para que un
Tipo de Dato sea correcto

Invariates

  • Fracción: no puede tener denominador 0
  • Persona: Id no puede ser nulo
  • Batalla Naval: Un tablero inicia con 5 barcos
  • Etc.

Un correcto uso de Tipos de Datos permite asegurar que un programa es correcto

Métodos

Definen las operaciones que se pueden hacer
sobre un Tipo de Dato

Atributos

Definen la información
que sustentan los métodos

API

El conjunto de métodos públicos de un Tipo de Dato

El API es lo más
importante en un Tipo
de Dato porque es lo que forma un programa

Los atributos son detalles de implementación.
Suelen variar mucho
y eso es bueno.

Encapsulamiento

Permite proteger las invariantes y desacoplar otros tipos de datos de los detalles de implementación

Modificadores
de Acceso

 De menor a mayor acceso: private, (package), protected, public

Regla de Oro

Todo debe tener la
menor visibilidad posible.

Objetos

Objeto

Es una instancia
de una clase

Estado

Los valores que tienen
los atributos de un objeto en un instante dado

Una fracción, p.j.,
con estado:
denominador 1,
numerador 2

Constructor

¡La parte más
importante
de una clase!

Constructor

Es el lugar natural para proteger las invariantes

Constructor

¡Valide los parámetros!

public class Fracción {
   private int numerador;
   private int denominador;
   public Fracción(int numerador, int denominador) {
      if (denominador == 0) {
         throw new IllegalArgumentException();
      }
      this.numerador = numerador;
      this.denominador = denominador;
   }
}
public class Persona {
   private Cédula cédula;
   private Palabra nombre;
   private Palabra apellido;

   public Persona(Cédula cédula, Palabra nombre, Palabra apellido) {
      this.cédula = Objets.requireNonNull(cédula);
      this.nombre = Objets.requireNonNull(nombre);
      this.apellido = Objets.requireNonNull(apellido);
   }
}

Una clase debería tener
al menos un constructor con parámetros.
Entre más, mejor

Mutabilidad

Si un objeto puede
cambiar su estado entonces se dice que el tipo de dato es mutable

public class Fracción {

   private int numerador;
   private int denominador;
  
   public Fracción(int numerador, int denominador) {
      this.numerador = numerador;
      this.denominador = denominador;
   }

   public void inverso() {
      int temp = this.numerador;
      this.numerador = this.denominador;
      this.denominador = temp;
   }

   public String toString() {
      return numerador + "/" + denominador;
   }
}

Fracción, versión mutable

Fracción unMedio = new Fracción(1, 2);
unMedio.toString(); // 1/2
unMedio.inverso();
unMedio.toString(); // 2/1

Mutabilidad

Usualmente se produce
en métodos void

Mutabilidad

Es deseable evitarla
al máximo

public class Fracción {

   private int numerador;
   private int denominador;
  
   public Fracción(int numerador, int denominador) {
      this.numerador = numerador;
      this.denominador = denominador;
   }

   public Fracción inverso() {
      return new Fracción(denominador, numerador);
   }

   public String toString() {
      return numerador + "/" + denominador;
   }
}

Fracción, versión inmutable

Fracción unMedio = new Fracción(1, 2);
unMedio.toString(); // 1/2
unMedio.inverso();
unMedio.toString(); // ¡aún 1/2!
Fracción unMedio = new Fracción(1, 2);
unMedio.toString(); // 1/2
Fracción dos = unMedio.inverso();
unMedio.toString(); // ¡aún 1/2!
dos.toString(); // ¡2/1!

Constructor Vacío

Lleva a mutabilidad y clases que no pueden proteger sus invariantes, facilmente

// no está realmente construído
Fracción unMedio = new Fracción();
unMedio.setNumerador(1); // ya casi
unMedio.setDenominador(2); // ¿ahora si?

Es difícil saber cuándo el objeto está construído

Evite al máximo los constructores vacíos,
los setters y los métodos void en general

No todo debe ser
un JavaBean

Sólo úselos si utiliza un FW o librería que los necesite, como Hibernate o Gson

Single Responsibility Principle - SRP

SRP

Una clase debe tener una sola razón para cambiar

SRP

Un tipo de dato
solo debería realizar operaciones asociadas
con su estado

SRP

Los métodos deberían utilizar la mayoría de atributos

public class Fracción {
   private int numerador;
   private int denominador;   
   // código
   public Fracción inverso() {
      // utiliza todos los atributos!
      return new Fracción(denominador, numerador);
   }
   public static int mcd(int a, int b) {
      // no usa ningún atributo :(
      // solo depende de los parámetros
      return b == 0 ? a : mcd(b, a % b);
   }
}

SRP

Todo método que no
utiliza atributos debe ser marcado como static

SRP

Los métodos static usualmente pertenecen a una clase Utilitaria

Clase Utilitaria

Clase sin estado que sólo tiene métodos estáticos
y constantes

SRP

Operaciones que dependan fuertemente del estado
de un objeto o que puedan romper una invariante deben ir en la clase
de ese objeto

public class Fracción {
   // código
   public Fracción sumar(Fracción otra) {
      int MCD = OperacionesAritmética(this.denominador, otra.denominador);
      int a = this.denominador / MCD * otra.numerador;
      int b = otra.denominador / MCD * this.numerador;
      return new Fracción(a + b, MCD);
   }
}

SRP

sumar() depende del estado de las fracciones
y puede romper las invariantes, lógicamente debe ser parte de Fracción

Expresividad

¿Cómo modelamos una Persona que tiene cédula, nombre y apellido?

public class Persona {

   private String cédula;
   private String nombre;
   private String apellido;

}

¿Todo es un String?

¿Qué es un String?

A un alto nivel: una secuencia, sin límite
de longitud, de
cualquier caracter
que un computador
pueda interpretar

¿Se parece a una cédula,
un nombre o un apellido?

¡NO!

public class Persona {

   private Cédula cédula;
   private Nombre nombre;
   private Nombre apellido;

}
public class Cédula {

   private String número;

   public Cédula(String número) {
      // puedo forzar invariantes
      // debe tener sólo dígitos
      // tener entre 8 y 10 dígitos
      // también puedo limpiar la
      // la representación
      this.número = número;
   }
}
public class Nombre {

   private String nombre;

   public Cédula(String nombre) {
      // puedo forzar invariantes:
      // debe tener letras
      // también puedo limpiar la
      // la representación
      this.nombre = nombre;
   }
}

¿Qué tal un libro?

public class Libro {
   private String título;
   private String isbn;
   private String autor;
   private String fechaDePublicación;
}

¡Todo es un String!

public class Libro {
   private Frase título;
   private ISBN isbn;
   private NombreCompleto autor;
   private Calendar fechaDePublicación;
}
public class Frase {
   public String frase;

   public Frase(String frase) {
      // solo letras, espacios
      // signos de puntuación
      this.frase = frase;
   }
}
public class NombreCompleto {
   private Nombre nombre;
   private Nombre[] nombresIntermedios;
   private Nombre apellido;

}
public class ISBN {
   private String isbn;

   public ISBN(String isbn) {
      // invariantes sobre longitud
      // dígitos de verificación
      this.isbn = isbn;
   }
}

¿Es una fecha de publicación de un
libro un Calendar?

Un Calendar representa
un instante específico
en el tiempo en un territorio específico

Una fecha de publicación es simplemente
la combinación de un
mes y un año

public class Libro {
   private Frase título;
   private ISBN isbn;
   private NombreCompleto autor;
   // YearMonth es parte de Java 8
   private YearMonth fechaDePublicación;
}

¿Una Carta de una baraja?

Una Carta tiene un
número y un palo

El número solo tiene 13 valores posibles: A, 2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q o K

El palo solo tiene 4 valores posibles: trébol, pica, corazón o diamantes

Un enum representa
un tipo de datos con
un conjunto finito de valores posibles

public enum Número {
   AS, DOS, TRES, CUATRO, CINCO, 
   SEIS, SIETE, OCHO, NUEVE, DIEZ, J, Q, K;
}
public enum Palo {
   TRÉBOL, PICA, CORAZÓN, DIAMANTE;
}
public enum Carta {
   private Número número;
   private Palo palo;

   public Carta(Número número, Palo palo) {
      this.número = número;
      this.palo = palo;
   }
}
Carta asCorazones = new Carta(Número.AS, Palo.CORAZÓN);
Carta jotaTrébol = new Carta(Número.J, Palo.TRÉBOL);

Conclusiones

La POO se basa en
la creación de nuevos
tipos de datos y como
se comunican estos
a través de su API

Los tipos de datos protegen sus invariantes mediante
el encapsulamiento

Crear clases inmutables evita todo tipo
de problemas en
los programas

Solo utilice JavaBeans
en clases puntuales
cuando un FW o una
librería lo requiera

SRP: busqué crear tipos
de datos cohesivos 

Q&A

La Travesía del Programador: Tipos de Datos

By Carlos Obregón

La Travesía del Programador: Tipos de Datos

La Travesía del Programador comienza con los Tipos de Datos

  • 1,774