Programando con
Tipos de Datos

¿Qué es un
Tipo de Dato?

Fracción unMedio = new Fracción(1, 2);
Fracción dosTercios = new Fracción(2, 3);
Fracción resultado = unMedio.por(dosTercios);
public class Fracción {

  private int denominador;
  private int numerador;

  // código 
}
public class Fracción {
  // código

  public Fracción inverso() {
    // código
  }

  public Fracción más(Fracción otra) {
    // código
  }

  public Fracción menos(Fracción otra) {
    // código
  }

  // código
}
public class Fracción {
  private int denominador;
  private int numerador;

  public Fracción(int numerador, int denominador) {
    if (denominador == 0) {
      throw new IllegalArgumentException("...");
    }
    this.numerador = numerador;
    this.denominador = denominador;
  }
 
  // código
}
public final class Fracción {

  private final int numerador;
  private final int denominador;

  public Fracción(int numerador, int denominador) {
    if (denominador == 0) {
       throw new IllegalArgumentException("...");
    }
    this.numerador = numerador;
    this.denominador = denominador;
  }

  public Fracción inverso() {
    // código
  }

  public Fracción más(Fracción otra) {
    // código
  }
}
public class Fracción {

  private int numerador;
  private int denominador;

  public void setNumerador(int numerador) {
    this.numerador = numerador;
  }

  public void setDenominador(int denominador) {
    this.denominador = denominador;
  }

  public void inverso() {
    // código
  }

  public void más(Fracción otra) {
    // código
  }
}

😔

Crear Tipos de Datos

  • Código más legible
  • Definir invariantes
  • Código menos propenso a errores
  • No todo necesita getters
  • Casi nada necesita un setter

Inmutabilidad

  • Clases que no son
    extendidas, deben ser final
  • Atributos final
  • Ningún método modifica el estado
  • Métodos retornan un nuevo objeto
    con el estado actualizado
  • Compartir Mutabilidad es Peligroso

Métodos

  • Métodos que no usen el estado de un
    objeto deben ser marcados como static
  • Un método static en una clase
    utilitaria es fácilmente reutilizable
  • Las clases y los métodos pueden coexistir

Clases Utilitarias

  • Clase final
  • Constructor privado que lanza AssertionError
  • Sólo métodos estáticos y constantes

DRY

Don't Repeat Yourself!

SRP

Single Responsibility Principle

¿Cómo se
construye un Objeto?

Constructores

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;
  }

  // código
}

Entre más mejor

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 Fracción(int numerador) {
    this(numerador, 1);
  }

  // código
}
// grados
Ángulo alfa = new Ángulo(180, true);
// radianes
Ángulo beta = new Ángulo(2 * Math.PI, false);

😔

Static Factory Methods

Ángulo alfa = Ángulo.grados(180);
Ángulo beta = Ángulo.radianes(2 * Math.PI);

😊

public final class Ángulo {
  private final double magnitud;
  private final boolean grados;

  private Ángulo(double magnitud, boolean grados) {
    this.magnitud = grados 
        ? magnitud % 360.0 
        : magnitud % (2 * Math.PI);
    this.grados = grados;
  }

  public static Ángulo grados(double magnitud) {
    return new Ángulo(magnitud, true);
  }

  public static Ángulo radianes(double magnitud) {
    return new Ángulo(magnitud, true);
  }

  // código
}

¿Qué pasa si tenemos
muchos atributos
y/o atributos opcionales?

public final class Triángulo {

  private final Ángulo alfa;
  private final Ángulo beta;
  private final Ángulo gamma;

  private final Longitud a;
  private final Longitud b;
  private final Longitud c;

  // código
}
public final class Triángulo {

  public Triángulo(Ángulo alfa, Longitud c, Ángulo beta) {

  }

  public Triángulo(Longitud a, Longitud b, Longitud c) {

  }

  public Triángulo(Longitud a, Ángulo gamma, Longitud b) {

  }

  // ahora todos con 4 parámetros

  // ahora todos con 5 parámetros

  // ahora con 6 parámetros
}

😔

Triángulo t1 = new Triángulo.Builder()
                            .alfa(Ángulo.grados(60))
                            .c(new Longitud(10))
                            .beta(Ángulo.grados(60))
                            .build();

Triángulo t2 = new Triángulo.Builder()
                            .a(new Longitud(3))
                            .gamma(Ángulo.grados(90))
                            .b(new Longitud(4))
                            .build();
public static class Builder {
  private Longitud a;
  private Longitud b;
  private Longitud c;

  // código

  public Builder a(Longitud a) {
    this.a = a;
    return this;
  }

  public Builder b(Longitud b) {
    this.b = b;
    return this;
  }

  public Builder c(Longitud c) {
    this.c = c;
    return this;
  }

  // código
}
public static class Builder {
  private Ángulo alfa;
  private Ángulo beta;
  private Ángulo gamma;

  // código

  public Builder alfa(Ángulo alfa) {
    this.alfa = alfa;
    return this;
  }

  public Builder beta(Ángulo beta) {
    this.beta = beta;
    return this;
  }

  public Builder gamma(Ángulo gamma) {
    this.gamma = gamma;
    return this;
  }

  // código
}
public class Triángulo {

  // código

  private Triángulo(Builder builder) {
    verificarInvariantes(builder);

    this.alfa = builder.alfa;
    this.beta = builder.beta;
    this.gamma = builder.gamma;

    this.a = builder.a;
    this.b = builder.b;
    this.c = builder.c;
  }

  // código

  public static class Builder {
    // código

    public Triángulo build() {
      return new Triángulo(this);
    }
  }  
}

¿Cómo restringimos quién puede hacer ciertos objetos?

public final class Tablero {
  private final int hileras;
  private final int columnas;
  private Map<Integer, Coordenada> fichas;

  private static final class Coordenada {
    private final int hilera;
    private final int columna;

    public Coordenada(int hilera, int columna) {
      assert hilera >= 0 && hilera >= hileras 
          && columna >= 0 && columna >= columnas : "...";
      this.hilera = hilera;
      this.columna = columna;
    }

    int getHilera() {
      return this.hilera;
    }

    int getColumna() {
      return this.columna;
    }
  }
}
public Tablero() {
  this.fichas = HashMap<>();

  var número = 1;
  for (var hilera = 1; hilera <= hileras; ++hilera) {
    for (var columna = 1; columna <= columna; ++columna) {
      fichas.put(número, new Coordenada(hilera, columna));
      ++número;
    }
  }

  revolver();
}

Clases Inner

  • Public / Static : Cualquiera puede crearlas,
    los nombres quedan asociados
  • Public / No-Static: La clase externa es una fábrica, cualquiera puede usarlas (p.j. Iterators en Java)
  • Private / No-Static: La clase externa es una fábrica, objetos de la clase inner son utilitarios
  • Private / Static: ¿?

Expresividad

public final class Persona {
  private final String nombre;
  private final String apellido;
  private final long cédula;
  private Calendar cumpleaños;
  
  // código
}

¿Qué es un nombre?

¿Qué es una cédula?

¿Qué es un String?

¿Qué es un long?

public final class Persona {
  private final Nombre nombre;
  private final Cédula cédula;
  private MonthDay cumpleaños;
  
  // código
}
public final class Nombre {
  private final String nombre;
  private final String segundoNombre;
  private final String apellido;

  // código
}
public final class Cédula {
  private final String id;

  // código
}
new Cédula("3312374");
new Cédula("3'312.374");
new Cédula("3 312 374");

👍

public class Libro {
  private ?? título;
  private ?? autor;
  private ?? fechaPublicación;
  private ?? isbn;

  // código
}
public class Libro {
  private String título;
  private Nombre autor;
  private YearMonth fechaPublicación;
  private ISBN isbn;

  // código
}
public final class Carta {
  private ?? número;
  private ?? palo;

  // código
}
public enum Número {
  AS, DOS, TRES, CUATRO, CINCO, 
  SEIS, SIETE, OCHO, NUEVE, 
  DIEZ, J, Q, K;
}
public enum Palo {
  CORAZÓN, TREBOL, 
  DIAMANTE, PICA;
}
public final class Carta {
  private Número número;
  private Palo palo;

  public Carta(Número número, Palo palo) {
    this.número = Objects.requireNonNull(número);
    this.palo = Objects.requireNonNull(palo);
  }

  // código
}
public enum Número {
  AS, DOS, TRES, CUATRO, CINCO, SEIS, 
  SIETE, OCHO, NUEVE, DIEZ, J, Q, K;

  public int puntaje() {
    return this.ordenal() + 1;
  }

  public String toString() {
    switch(this) {
      case DOS: case TRES: case CUATRO: case CINCO:
      case SEIS: case SIETE: case OCHO: case NUEVE:
        return Integer.toString(puntaje());
      case AS:
        return "A";
      case DIEZ:
        return "T";
      case J: case Q: case K:
        return super.toString();
    }
  }
}
public enum Palo {
  CORAZÓN {
    public String toString() {
      return "♥";
    }
  }, 
  TREBOL {
    public String toString() {
      return "♣";
    }
  }, 
  DIAMANTE {
    public String toString() {
      return "♦";
    }
  }, 
  PICA {
    public String toString() {
      return "♠";
    }
  };
}
public final class Carta {
  private Número número;
  private Palo palo;

  public Carta(Número número, Palo palo) {
    this.número = Objects.requireNonNull(número);
    this.palo = Objects.requireNonNull(palo);
  }

  public int puntaje() {
    return número.puntaje();
  }

  public String toString() {
    return String.format("%s%s", número, palo);
  }
}

Expresividad

  • ¡No todo es un String!
  • Piensa en el dominio que modelas
  • Usa Enum para variables con
    un conjunto finito de valores

NULL

¡Nunca uses NULL!

Pruebas Unitarias

Juego de la Vida

......
...#..
.#..#.
.#..#.
..#...
......

Reglas

  1. Una célula viva con menos de 2 vecinos vivos, muere por sub población.
  2. Una célula viva con 2 o 3 vecinos vive en la siguiente generación.
  3. Una célula viva con más de 3 vecinos vivos, muere por sobre población.
  4. Una célula muerta con exactamente 3 células vivas, se vuelve viva por reproducción.
......
...#..
.#..#.
.#..#.
..#...
......
......
......
..###.
.###..
......
......

¡Gracias!

@gaijinco

Programando con Tipos de Datos

By Carlos Obregón

Programando con Tipos de Datos

  • 1,399