Skip to content

6 Técnicas

Encadenamiento de llamadas a métodos

Se emplea cuando invocamos a un método de un objeto que nos devuelve como resultado otro objeto al que podemos volver a invocar otro método y así encadenar varias operaciones:

public class CallsToMethods {
    public void show(){
        Boolean b;
        String string;

        string = "EntornosDeDesarrollo";
        System.out.println(string.substring(10).toUpperCase()); //DESARROLLO

        b = Boolean.TRUE;
        System.out.println(b.toString().charAt(2));
    }

    public static void main(String[] args) {
        new CallsToMethods().show();
    }
}

En este ejemplo, el método substring(10) está devolviendo una subcadena de la cadena string a partir del carácter 10 empezando en 0, es decir, "Desarrollo", al que se le invoca luego el método toUpperCase devolviendo como resultado la cadena "DESARROLLO".

Y el método toString() de la variable b de tipo Boolean está devolviendo la cadena "true" a la que se le encadena el método charAt(2) devolviendo el carácter 'u'.

Sintaxis fluida

Cuando un método modifica algún atributo del objeto, se puede devolver el objeto con un return para que dicho método pueda insertarse en una expresión. Esto permite encadenar otras llamadas de métodos consiguiendo que el código sea más corto, más legible y más fácil de manejar para los programadores.

Veamos un ejemplo de clases, una con sintaxis fluida y otra sin ella para ver la diferencia.

public class Vehicle {
    private int wheelCount; // Nº ruedas
    private double speed; // Velocidad
    private String color; // Color del vehículo

    public Vehicle(){
        this(4, "Blanco");
    }

    public Vehicle(int wheelCount, String color) {
        this.wheelCount = wheelCount;
        this.color = color;
        speed = 0;
    }

    public int getWheelCount() {
        return wheelCount;
    }

    public void setWheelCount(int wheelCount) {
        this.wheelCount = wheelCount;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getSpeed() {
        return speed;
    }

    public void accelerate(double amount){ // acelerar
        speed += amount;
    }
    public void brake(double amount){ // frenar
        speed -= amount;
    }
}
public class Vehicle2 {
    private int wheelCount; // Nº ruedas
    private double speed; // Velocidad
    private String color; // Color del vehículo

    public Vehicle2(){
        this(4, "Blanco");
    }

    public Vehicle2(int wheelCount, String color) {
        this.wheelCount = wheelCount;
        this.color = color;
        speed = 0;
    }

    public int getWheelCount() {
        return wheelCount;
    }

    public void setWheelCount(int wheelCount) {
        this.wheelCount = wheelCount;
    }

    public String getColor() {
        return color;
    }

    public Vehicle2 setColor(String color) {
        this.color = color;
        return this;
    }

    public double getSpeed() {
        return speed;
    }

    public Vehicle2 accelerate(double amount){ // acelerar
        speed += amount;
        return this;
    }
    public Vehicle2 brake(double amount){ // frenar
        speed -= amount;
        return this;
    }
}
public class FluidSyntax {
    public void show(){
        Vehicle car1;
        Vehicle2 car2;

        car1 = new Vehicle(4, "rojo");
        car2 = new Vehicle2(4, "azul");

        car1.accelerate(120.55);
        car1.brake(20.32);

        System.out.printf("Velocidad del coche1: %.2f\n", car1.getSpeed());

        System.out.printf("Velocidad del coche2: %.2f\n", car2.accelerate(120.55).brake(20.32).getSpeed());
    }

    public static void main(String[] args) {
        new FluidSyntax().show();
    }
}
Velocidad del coche1: 100,23
Velocidad del coche2: 100,23

Invariante de una clase

El invariante de una clase es el conjunto de restricciones que deben cumplir todos los objetos que se instancien de dicha clase, como por ejemplo:

  • Restricciones sobre los valores que pueden tomar los atributos de la clase. Ejemplo: el atributo wheelCount de la clase Vehicle tiene que ser mayor que 0.
  • Restricciones que afecten a más de un atributo. Por ejemplo, si una clase modela un rango de valores, el atributo que corresponda al límite superior del rango debe ser forzosamente mayor o igual que el atributo que corresponda al límite inferior del rango, dada la definición matemática del rango.
  • Restricciones con respecto a los objetos con los que se relaciona. Ejemplo: un empleado tiene un atributo que es objeto de la clase Empresa donde trabaja. Una restricción posible sería que dicha empresa no pueda ser nula.

Los métodos constructores de una clase deben respetar el invariante de la clase a la hora de construir los objetos.

Los métodos públicos de la clase también deben respetar el invariante. Un método puede no respetar el invariante en el transcurso de la ejecución, pero cuando el método finalice, el invariante se tiene que cumplir, es decir, es perfectamente viable que un método para alcanzar su objetivo pueda perder de forma temporal el invariante pero siempre y cuando finalice con el invariante cumplido, es decir, antes de la llamada el invariante se debe cumplir y después de la llamada también, durante la ejecución del mismo puede no satisfacerse.

Únicamente se deben satisfacer los invariantes en las llamadas a métodos públicos, la ejecución de métodos privados de la misma clase pueden saltarse esta norma aunque no es aconsejable.

Definir invariantes de clase puede ayudar a los programadores y controladores de calidad a localizar más errores durante las pruebas de software.

Encapsulamiento

La encapsulación es un principio fundamental de la programación orientada a objetos que consiste en ocultar el estado o los atributos de un objeto y obligar a que toda interacción se realice a través de los métodos del objeto definidos en su clase para conservar su invariante.

El encapsulamiento se consigue utilizando los modificadores de acceso. Se recomienda que los atributos de una clase sean privados, por lo tanto, aquellos atributos que se permitan consultar deben tener sus propios métodos get, y los que se permitan modificar, deben tener sus propios métodos set. Hacer uso de este convenio nos facilitará trabajar con el resto del mundo y nos permitirá ampliar las capacidades de nuestro código utilizando frameworks existentes que hacen uso del convenio y que si no seguimos no podremos utilizar.

public class Vehicle {
    public int wheelCount; // Nº ruedas
    public double speed; // Velocidad
    public String color; // Color del vehículo

    public Vehicle(int wheelCount, String color) {
        this.wheelCount = wheelCount;
        this.color = color;
        speed = 0;
    }

    public void accelerate(double amount){ // acelerar
        speed += amount;
    }
    public void brake(double amount){ // frenar
        speed -= amount;
    }
}
public class NoEncapsulation {
    public void show(){
        Vehicle car = new Vehicle(4, "azul");

        car.accelerate(90.54);
        System.out.printf("Velocidad: %.2f\n", car.speed);

        car.speed = 120; // Se accede directamente
        System.out.printf("Velocidad: %.2f\n", car.speed);
    }

    public static void main(String[] args) {
        new NoEncapsulation().show();
    }
}

Podemos observar que no se está cumpliendo el principio de encapsulamiento ya que el cliente está accediendo directamente al atributo speed modificando incorrectamente su valor ya que la velocidad solamente se debe modificar acelerando o frenando el vehículo.

El ejemplo anterior pero cumpliendo encapsulamiento ocultando el estado o los atributos del objeto y obligando a toda interacción se realice a través de los métodos del objeto definidos en su clase, sería tal y como hemos estado viendo en ejemplos anteriores, es decir, haciendo privado sus atributos y creando los métodos getters y setters necesarios.

public class Vehicle{
    private int wheelCount; // Nº ruedas
    private double speed; // Velocidad
    private String color; // Color del vehículo

    public Vehicle(){
        this(4, "Blanco");
    }

    public Vehicle(int wheelCount, String color) {
        this.wheelCount = wheelCount;
        this.color = color;
        speed = 0;
    }

    public int getWheelCount() {
        return wheelCount;
    }

    public void setWheelCount(int wheelCount) {
        this.wheelCount = wheelCount;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public double getSpeed() {
        return speed;
    }

    public void accelerate(double amount){ // acelerar
        speed += amount;
    }
    public void brake(double amount){ // frenar
        speed -= amount;
    }
}

Método toString

El método toString() se utiliza para obtener una cadena de texto que represente al objeto. Lo veremos en más profundidad en el tema de herencia. Veamos su uso:

public class Vehicle {
    //...

    @Override
    public String toString() {
        return "Vehicle [wheelCount =" + wheelCount + ", speed=" + speed + ", color =" + color + "]";
    }
}
public static void main(String[] args){
    Vehicle car = new Vehicle(4, "rojo");

    System.out.println(car.toString());
}

Algunos IDEs pueden generar toString() de forma automática.

El método System.out.println y sus derivados llaman automáticamente al método toString si se le pasa el objeto

public static void main(String[] args){
    Vehicle car = new Vehicle(4, "rojo");

    System.out.println(car); // Es lo mismo que car.toString()
}