Encapsulamiento

Toca el turno al concepto de la programación orientada a objetos llamado "encapsulamiento".

Como su nombre lo indica.....

Bueno mejor un ejemplo:

class Persona {
    String nombre;
    int edad;
    int edad() {
        return this.edad;
    }
}

Este código define una clase llamada Persona con dos atributos nombre y edad.

Aunque parece trivial y obvio este es uno ( otro ) de los aspectos más importantes de la programación orientada a objetos; definir los datos y los métodos para acceder/modificar los datos en el mismo lugar.

A este simple concepto se le llama "encapsulamiento" pues los datos están dentro de la capsula que forma la definición de la clase.

Y.... yap!.. eso es todo en serio. Es un concepto muy fácil de entender.

Ahora, para máximizar la eficacia de este concepto algunos lenguajes de programación como Java permiten tener diferentes "modificadores de acceso" para restringir o permitir más o menos visibilidad de los atributos ( modificar la forma en la que esos datos pueden ser accedidos ).

Lo más recomendado es tener el modificador de acceso más restrictivo para los atributos, de forma que además de encapsulados queden ocultos evitando que alguien modifique por error los valores. En Java el modificador de acceso para hacer esto es private

class Persona {
    private String nombre;// <-- ya nadie lo puede ver
    private int age;// <-- ya nadie lo puede ver
    public int age() {
        return this.age;
    }
}

A los métodos también se les puede aplicar distintos modificadores de acceso, por lo que un método puede ser también marcado como private.

La recomendación general es inicialmente hacer todo privado e irlo haciendo público conforme se va necesitando. Entre menos métodos públicos tenga una clase es más fácil de entender. No se recomienda tener atributos públicos en lo absoluto.

Y.. ya.. el encapsulamiento es un concepto sencillo de explicar y de entender por lo que no le queda bien un post largo así que aquí lo voy a dejar.

Solo queda mencionar que hay que tener cuidado al usar getters y setters pues se podría poner en riesgo el encapsulamiento, sobre especialmente cuando se manejan objetos mutables, pero de estoy ya hablaremos después.

p.d. Alguien sabe que atributos tiene internamente la clase java.lang.String sin haber visto el código? Bueno eso se debe a que sus atributos están ocultos y bien encapsulados. :)

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

colecciones

Podrias extender un poco el post si mencionas algo de colecciones... el ejemplo típico es que tienes un objeto con una colección interna mutable, para la cual quieres poner un getter; el getter debería devolver una copia inmutable de la colección, porque si devuelves directamente la colección, alguien la puede modificar por fuera.

public class Ejemplo {
  private List<String> lista = new ArrayList<String>();

  //puedes tener metodos que internamente modifican esa lista

  //el getter
  public List<String> getLista() {
    return Collections.unmodifiableList(lista);
  }
}

Ahí hay una ventaja clara del encapsulamiento... otra es validación de datos:

public class OtroEjemplo {
  private String nombre;
  public void setNombre(String value) {
    if (nombre != null) {
      throw new IllegalStateException("El nombre solamente puede ser fijado una vez");
    }
    if (value==null) {
      throw new IllegalArgumentException("Nombre no puede ser nulo");
    }
    if (value.length() < 4 || value.length() > 50) {
      throw new IllegalArgumentException("Nombre debe medir entre 4 y 50 caracteres");
    }
    nombre = value;
  }
}
Imagen de AlexSnake

Uuuoooooraalesss

Mas fácil que los conceptos de la uni... pero ya que mencionas los modificadores de acceso, es cierto que es una mala práctica de programación el no poner el modificador de acceso a la variable?? y ya mencionado esto, habrá un post o alguien conoce donde puedo saber más sobre las buenas prácticas de programación.

@AlexSnakeYeap, es mala

@AlexSnake
Yeap, es mala práctica, sobre todo si se hace sin una razón clara.

Lo puse ahí en el post, pero como lo veía tan ... mmhh simple ya no quize profundizar en el tema:


"Lo más recomendado es tener el modificador de acceso más restrictivo para los atributos, de forma que además de encapsulados queden ocultos evitando que alguien modifique por error los valores. En Java el modificador de acceso para hacer esto es private"

Por ejemplo una buena razón para hacer esto es porque estas escribiendo un ejemplo y no poner modificadores de acceso me hace escribir menos ( yo lo hago todo el tiempo acá en JavaMexico ) Pero en código real ( usado por alguien más ) no haría eso.

También es mala práctica poner getters / setters a todo nomás porque sí.

Es decir el criterio de que algo está mal o no es si lo haces nomás porque así lo viste, pero en realidad no entiendes sus implicaciones.

Por ejemplo en común en los JSP's o incluso en archivos Java tener en un import todo:

import java.awt.*;
import java.util.*;
import java.io.*;
import java.text.*;

class BLablabal

...

Nomás porque "así es como compila". Si tienes una razón contundente y razonada puedes hasta hacer que todos los atributos sean públicos, pero si no la tienes lo mejor es hacer que todos los atributos ( e incluso los métodos ) sean privados hasta que esa razón aparezca. Me entiendes?

Encapulamiento, mutabilidad y copias defensivas.

@ezamudio Si es muy importante ese tema, sobre todo en el aspecto de los objetos mutables.

Por ejemplo, si tuvieramos una clase mutable que fuera ... ... mmmhhh Telefono:

public class Telefono {
   private int codigo;
   private String numero;

   // Constructor
   public Telefono( int codigo,  String numero )  {
       this.codigo = codigo;
       this.numero = numero;
   }
 
   // Getters y setters.
   public void setCodigo( int codigo ) {
        this.codigo = numero ;
    }
    public int getCodigo() {
        return this.codigo;
    }
    public void setNumero( String numero ) {
        this.numero = numero;
    }
    public String getNumero( String numero ) {
        return this.numero;
    }
}

Y si la persona tuviera un telefono, hay un gran riesgo de exponer ese atributo telefono porque al ser mutable puede modificar nuestro propio estádo:

Imaginemos que un usuario solo puede tener un telefono con terminacion 03 y si no lo tiene debe de lanzar un error:

public class Persona  {
    private final Telefono telefono;
    public Persona( Telefono unTelefono ) {
        if ( unTelefono != null && unTelefono.endsWith("03" )  {  // no sé solo lo inventé
            this.telefono = conTelefono;
        }  else {
            throw new IllegalArgumentException("El telefono no puede ser nulo y debe de terminar en '03'");
       }
       
     }
     public Telefono getTelefono() {
         return this.telefono;
     }
     public void marca() {
           // Como ya validamos que el telefono sea 03 no hay problema y puede hacer
           // blah blah bla
     }
}

Ok, una persona tiene un telefono, y es declarado como una variable final ( o sea que no se le puede asignar un nuevo telefono ) y tampoco hay setter para el Telefono, así que se podría tener la falsa creencia que este valor está seguro y que podemos confiar en él.

Se hace una validación para ver que el telefono efectivamente termine en 03 ( es una regla que me acabo de inventar )

Parece que todo esta bien encapsulado y demás , el problema con este código es que como la clase Telefono es mutable, ya no se puede confiar en ella. Supongamos que el código del método marca() confía en que ya el telefono es válido para marcar, si no termina en 03... no sé le cobran el triple o algo.

Que pasaría si alguién le pide el telefono a la persona:

....
Telefono t = persona.getTelefono();

Pues que ese código ahora puede hacer lo que quiera con el telefono... por ejemplo cambiarle el número

t.setNumero("5658-1111");
persona.marca();//<-- ya no termina en 03

O bien lo pone en nulo

t.setNumero( null ) ;
persona.marca(); // probablemente lanze NullPointerException

La solución incorrecta es agregar validaciones en el método marcar!... Esto se ha vuelto tan común que ya nadie piensa en la razón de esto y culpan al lenguaje! "Java es horrible porque tiene nulos y puede lanzar NullPointerException" y muchas veces se termina haciendo cosas como:

public void marcar() {
    // como me tronó lo que
   // soluciona es validar si es nulo
   if ( t == null )  {  
       return; // no marques
   }
   if ( ! t.endsWith( "03" ) )  {
      return ; // no marques...
   }
   ... continuar con la logica
}

Esta es la forma más común y erronea de solucionar el problema. No deberíamos de andar revisando nuestras reglas todo el tiempo. Acá parece poca cosa, pero cuando se tiene un sistema con 10 - 20 métodos y todos andan revisando siempre la misma cosa, solo porque ya no confian en sí mismos ( en sus atributos internos y privados!!! ) entonces hay algo mal. Luego este código se vuelve más y más difícil de leer y mantener.

La solución no es agregar duplicar las validaciones, sino regresar el control a donde se necesita:

// Get telefono corregido.
public Telefono getTelefono() {
    return new Telefono( this.telefono.getCodigo(), this.telefono.getNumero() );
}

Así es, crear una copia. Y así puedo seguir confiando en mi teléfono, porque aunque por fuera se le cambien los valores:

Telefono t = persona.getTelefono();
t.setNumero( null );
persona.marca();

Mi método marca sigue funcionando bien. No tengo que repetir las validaciones ni nada. Sigo confiando en mis atributos.

Pero la queja está en que estoy teniendo que crear otro objeto ( ahora tengo 2 objetos con la misma información ) y eso consume más memoria, debería de ser innecesaario etc.

Pues la culpa la tiene el objeto mutable telefono y es necesario hacer esto para evitar que falle.

Una alternativa todavía mejor aún hubiera sido tener la clase Telefono como inmutable. De esa forma no se necesita crear una copia, porque como es inmutable pueees, no cambia el valor, una vez validada podemos seguir confiando en ella, no hay que crear nuevos datos ni nada.

//Un telefono inmutable
public final class Telefono {
    private final int codigo;
    private final String numero;
    public Telefono( int codigo, String numero ) {
        this.codigo = codigo;
        this.numero = numero;
    }
    public int getCodigo() { return this.codigo; }
    public String getNumero() return this.numero; }
}

Y listo, una vez creado el telefono no cambiará su número y la clase persona puede confiar en él sin tener que hacer copias defensivas.

class Persona {
 ....
    public Telefono getTelefono() {
        return this.telefono; // si, ya es seguro de nuevo
    }
}

Un efecto similar se puede presentar con los threads y la sincronización. Un objeto no mutable no necesita sincronización y el código es más simple, pero esta entrada ya es suficientemente larga.

Imagen de Sr. Negativo

Buena explicación

@OscarRyz

Buena explicación sobre polimorfismo y encapsulación.

Imagen de beto.bateria

Se supone que las

Se supone que las validaciones deberian hacerse en los setters, y si hay una excepcion pues controlarla, mencionando que hay un error en la informacion, asi el usuario la deberia de solucionar o al menos saber que existe ese error.

Ademas los valores nulos se deberian de utilizar.

Imagen de AlexSnake

Ok entendido.

Gracias Oscar por la explicacion ahora me queda mas claro y sobre lo que mencionas de la mutabilidad, entonces se puede resumir que solo se trata de modificar el valor de la variable mediante un set.

Imagen de gabrielsimpsons

en java 7

Solo queria aportar como se veria el codigo de @ezamudio en java 7

public class Ejemplo {
  private List<String> lista = new ArrayList<>(); //Operador diamante ^. ^

  //puedes tener metodos que internamente modifican esa lista

  //el getter
  public List<String> getLista() {
    return Collections.unmodifiableList(lista);
  }
}

Imagen de ezamudio

otros lenguajes

Los avances en Java 7 la verdad se quedaron muy cortos. Ese mismo código en Groovy y Scala sigue siendo más corto:

class Ejemplo {
  private def lista=new ArrayList()
  //si el código será usado desde Java, es conveniente dejarle el tipo de retorno, si no bien podría ser def
  List<String> getLista() {
    Collections.unmodifiableList(lista)
  }
class Ejemplo {
  val lista=new java.util.ArrayList()
  def getLista()=java.util.Collections.unmodifiableList(lista)
}

Ah si?

pueees...

import ( java.util.* )
un.Ejemplo {
    lista = ArrayList()
    getLista() : List {
        Collections.unmodifiableList( lista )
    }
}
Imagen de ezamudio

ryz no tiene keyword?

O sea que ryz no tiene keyword para definir variables? En lo personal eso nunca me ha gustado, hace el software más difícil de depurar; cuando ves entre un montón de código una línea que dice a=5, están declarando la variable ahí, o sólo actualizaron su valor y ya venía desde antes? Y si estoy manteniendo código, cómo sé que al insertar una variable nueva, no estoy sobreescribiendo una variable existente, cuyo valor se necesita leer más abajo?

En los lenguajes que vemos aquí regularmente, declaras la variable poniéndole el tipo, o val, o var, o def; el compilador se queja si ya existe una variable con el mismo nombre en ese mismo contexto, o te avisa si estás ocultando una variable de un contexto más externo (si el lenguaje lo permite).

Eso es algo que no me gustó mucho de Erlang; la primera vez que dices a=5 el runtime dice "ok, a vale 5". La siguiente vez que escribes a=5 obtienes true... WTF? ah pues es que como todo es inmutable en Erlang, las comparaciones las haces con un solo = y como a ya tiene un valor asignado, estás comparando si a vale 5. Si pones a=2 obtienes false. Prefiero el comportamiento y sintaxis de Scala, donde tienes que poner val a=5 y si luego se te ocurre hacer a=5 o a=2 el compilador se queja de que quieres modificar un valor inmutable.

por cierto, esa lista en ryz, es mutable? inmutable? bueno no la lista en sí (un ArrayList es mutable, no hay de otra), pero se puede asignar un ArrayList distinto a esa variable después, o ya no?

O sea que ryz no tiene

O sea que ryz no tiene keyword para definir variables?

Exacto

En lo personal eso nunca me ha gustado...

Si, hay que tener cuidado, lo mismo pasa con JavaScript, pero ahí si se puede poner var

Para asegurarse de no tomar una existente la "mejor práctica" es declarar el tipo de dato y listo!

demo.Scope {
    outer = "Yay"  //; atributo
    method() {
        outer : String = "nay" //; variable local
    }
    otherMethod() {
        outher = "Done"  //; atributo
    }
}

Otra opción que se consideró fue como en Python forzar a: self.outer o como en Ruby @outer , pero me parece mejor esta opción.

Eso es algo que no me gustó mucho de Erlang...

Ajap... es un riesgo, como decía las opciones para quién se quiere asegurar de que esto no suceda con declaración de tipo y/o usar self.

por cierto, esa lista en ryz, es mutable? inmutable?

Ahora mismo ( 22 sept 2011 ) es "non-final", pero solo porque no he encontrado un simbolo que represente "non-final". Cuando lo encuentre por omisión todos los atributos serán "leaf" ( final en Java ) y habrá que hacerlos non-final explicitamente.

El candidato más fuerte es el símbolo: ! ( admiración ) que significa: "Advertencia, el uso de este atributo tiene efectos secundarios" sin embargo tiene el gran inconveniente de confundirse con la negación en Java.

demo.Contador {
    ! indice : 0
    nombre = "X"
    incrementa() {
        indice = indice .+ 1 //; eventaulmente será indice.++()
        nombre = "Y" //; compilation error ( en el futuro )
    }
}

Otra opción es tener 1 palabra resevada "var" ( por variable )

demo.Contador {
    var indice : 0  //;  :-/ ... mmmhh no lo sé
    nombre = "X"
    incrementa() {
        indice = indice .+ 1 //; eventualmente será indice.++()
        nombre = "Y" //; compilation error ( en el futuro )
    }
}

Cuando haya definido esa parte entonces todos los atributos serán "leaf" o "final" por omisión. Pero hasta entonces, no tengo forma de poner variables "finals".

Alguien sugiere algo?