Objeto inmutable

De Wikipedia, la enciclopedia libre

En programación orientada a objetos y programación funcional un «objeto inmutable» es un objeto cuyo estado no puede ser modificado una vez creado.[1]​ Es el opuesto a los «objetos mutables», que pueden ser modificados tras su creación. En algunos casos un objeto puede ser considerado como inmutable aunque algunos de sus atributos internos cambie, siempre y cuando el estado del objeto parezca no cambiar desde un punto de vista externo al mismo. Por ejemplo, un objeto que use memoización para cachear los resultados de cálculos costosos puede ser considerado un objeto inmutable.

Los objetos inmutables suelen ser útiles dado que son seguros en entornos multihilo.[1]​ Otro beneficio es que son más fáciles de entender y razonar sobre ellos además de que ofrecen mayor seguridad que los objetos mutables.[1]

Trasfondo[editar]

En programación imperativa, los valores almacenados en variables cuyo contenido no cambia son conocidas como «constantes» para diferenciarlos de los valores que pueden ser alterados en tiempo de ejecución. Ejemplos de constantes serían constantes matemáticas, constantes físicas, factores de conversión o el valor de Pi con varios decimales.

En muchos lenguajes de programación orientada a objetos, se pueden utilizar alias a otros objetos, que suelen ser conocidos como referencias. Algunos ejemplos de estos lenguajes son Java, C++, C#, VB.NET, y varios lenguajes de programación interpretados como Python y Ruby. En el caso de usar referencias, la inmutabilidad de un objeto es importante cuando puede ser referenciado desde diferentes puntos del programa.

Dado que el tamaño de una referencia es en general menor que el objeto al que apunta (es habitual que las referencias tengan el tamaño de un puntero), si se tiene la seguridad de que un objeto es inmutable, este puede ser copiado de manera sencilla creando una referencia al mismo en lugar de copiar el objeto al completo, esto resulta en un ahorro de memoria y una notable mejora en la velocidad de ejecución del programa.

Copiar objetos mediante referencias es más difícil cuando se hace sobre objetos mutables, ya que si cualquier referencia a un objeto mutable cambia el objeto referenciado, el resto de referencias al objeto verán dicho cambio. Si este no es el efecto deseado, puede ser difícil notificar a las otras referencias cómo responder correctamente. En estas situaciones copiar el objeto al completo en lugar de copiar la referencia es una solución más fácil pero más costosa. El patrón de diseño conocido como observer (observador) es una alternativa para controlar cambios sobre objetos mutables.

Los objetos inmutables pueden ser útiles en aplicaciones multihilo ya que diferentes hilos pueden interactuar con los datos contenidos en los objetos inmutables sin preocuparse de que dichos datos vayan a ser cambiados en otros hilos. En consecuencia, los objetos inmutables son considerados más seguros entre hilos que los objetos mutables.

La práctica de usar referencias a objetos en lugar de copias del objeto original recibe el nombre de interning. Usando interning, dos objetos se consideran iguales si y sólo si sus referencias (habitualmente representadas como números enteros) son iguales. Algunos lenguajes como Python hacen esta comparación automáticamente. Si el algoritmo que implementa el interning garantiza su utilización en todo caso que sea posible, entonces el coste de comparar la igualdad de dos objetos se reduce a comparar sus referencias (lo que supone una ganancia de velocidad sustancial en muchas aplicaciones, ya que las referencias suelen implementarse mediante punteros). El interning generalmente sólo es útil para objetos inmutables.

En ocasiones, algunos de los campos de un objeto son inmutables. Es decir, esas partes del objeto no pueden ser cambiadas independientemente de que otras partes del mismo objeto sean modificables (este tipo de objetos se denominan débilmente inmutables). Si todos los campos de un objeto son inmutables, entonces el objeto completo puede considerarse inmutable. Si además el objeto no permite ser ampliado, es considerado fuertemente inmutable.[2]​ Esto permite forzar explícitamente ciertas invariantes sobre datos concretos en el objeto que se mantendrán sin cambios durante todo el ciclo de vida del mismo. En algunos lenguajes este comportamiento se consigue con el uso de una palabra clave (como const en C++, final en Java) que indica que el campo será inmutable. En otros lenguajes, sucede al contrario: en OCaml los campos de un objeto o registro son inmutables por defecto y necesitan ser declarados con la palabra clave mutable para permitir su modificación.

Implementación[editar]

La inmutabilidad no implica que el objeto sea almacenado en memoria de sólo lectura. Se trata más bien de una orden para que el compilador verifique las operaciones sobre el objeto y aborte la generación del programa en caso de que no se cumpla la inmutabilidad. Es decir, indica lo que el programador debe hacer a través de la interfaz normal del objeto, no necesariamente lo que puede hacer (por ejemplo, se puede dar un rodeo para engañar al sistema de tipos o violar la correctitud de constantes en C o C++).

Ada[editar]

En Ada, cualquier objeto se declara como variable (es decir: mutable, que es implícito por defecto), o constante (es decir: inmutable) mediante la palabra clave constant.

  type Algun_tipo is new Integer; -- puede ser cualquier cosa mas complicada
  x: constant Algun_tipo:= 1; -- inmutable
  y: Algun_tipo; -- mutable

Los parámetros son inmutables cuando se pasan como in y mutables en los modos in out y out.

  procedure Haz_algo(a: in Integer; b: in out Integer; c: out Integer) is
  begin
    -- a es inmutable
    b:= b + a;
    c:= a;
  end Haz_algo;

C++[editar]

En C++ los objetos que cumplen con la correctitud de constantes permiten al usuario declarar instancias tanto mutables como inmutables, a su vez, estos objetos proporcionarán dos versiones de los métodos de acceso a datos, dependiendo de si el objeto es mutable o inmutable se usará una u otra. Un ejemplo de esta funcionalidad es el operador de acceso a elementos operator[] de la clase vector de la Biblioteca estándar de C++.

const std::vector<int> vector_inmutable = { 1, 2, 3, 4 };
std::vector<int> vector_mutable = { 5, 6, 7, 8 };

// vector_mutable puede cambiar su contenido mediante la
// version no-constante de operator[]
vector_mutable[2] = 0;

// no se puede cambiar el contenido de vector_inmutable porque
// usa la version constante de operator[]
vector_inmutable[2] = 0; // <--- error de compilacion!

Si el objeto proporciona un campo de tipo puntero o referencia a otro objeto, entonces es posible modificar el objeto apuntado o referenciado desde un método constante, sin incumplir la correctitud de constantes. Podría decirse que en ese caso el objeto no es realmente inmutable.

C++ además provee inmutabilidad abstracta mediante la palabra clave mutable, que permite modificar un campo desde un método constante. Uno de los usos habituales de mutable es crear una clase que calcula un valor la primera vez que este es solicitado y a continuación almacena el resultado.

class Foo
{
private:
    mutable bool calculado;
    mutable int valor;
public:
    Foo() : calculado(false), valor(0) {}
    int obtenerValor() const
    {
        if (!calculado)
        {
            // Calcular valor…

            // Los campos ‘valor’ y ‘calculado’ se modifican
            // pese a que el metodo es constante.
            valor = valor_calculado;
            calculado = true;
        }

        return valor;
    }
};

Java[editar]

El típico ejemplo de objetos inmutables en Java, son las instancias de la clase String.

String caracteres = "ABC";
caracteres.toLowerCase();

El método toLowerCase() no cambiará los datos "ABC" contenidos en la cadena caracteres, en vez de eso, se crea un nuevo objeto String el cual recibe "abc" durante su construcción y se devuelve una referencia a este desde el método toLowerCase(). Para hacer que caracteres contenga la cadena "abc" es necesaria otra aproximación:

caracteres = caracteres.toLowerCase();

De esta manera caracteres referencia a un nuevo objeto String cuyo contenido es ”abc”. Nada en la declaración de la clase String indica que deba ser un objeto inmutable; más bien, ninguno de los métodos de la clase modifican en ningún momento los datos que contiene, haciendo que sea inmutable.

La palabra clave final se usa para implementar objetos inmutables,[3]​ pero por sí misma no puede hacer que otros objetos lo sean. Como por ejemplo:

Podemos evitar que los tipos de datos primitivos (int, long, short, etc.) puedan ser re-asignados una vez definidos:

int i = 42; // int es un tipo primitivo
i = 43; // Correcto

final int j = 42;
j = 43; // Error de compilacion! j es final: no puede reasignarse

Los objetos referenciados no pueden hacerse inmutables únicamente usando la palabra clave final, tan sólo previene su reasignación.

final MiObjeto obj = new MiObjeto(); // obj es una referencia a MiObjecto
obj.dato = 100; // Correcto. Podemos cambiar el estado del objeto obj (obj es mutable, final no cambia este hecho)
obj = new MiObjeto(); // Error de compilacion. obj es final, por lo que no puede ser reasignado.

Las clases que envuelven tipos primitivos (Integer, Long, Short, Double, Float, Character, Byte, Boolean) son todas inmutables. Las clases Inmutables pueden ser implementadas siguiendo unas directrices simples.[4]

Perl[editar]

En Perl, crear una clase inmutable requiere dos pasos: en primer lugar crear métodos de acceso (ya sea automática o manualmente) que prevengan la modificación de los atributos del objeto, y en segundo lugar, prevenir la modificación directa de los datos de las instancias de dicha clase (que generalmente se almacenan en un hash, y pueden ser bloqueadas con la función lock_hash de Hash::Util):

package Inmutable;
use strict;
use warnings;
use base qw(Class::Accessor);
# crecion de los metodos de acceso de solo lectura
__PACKAGE__->mk_ro_accessors(qw(value));
use Hash::Util 'lock_hash';

sub new {
	my $class = shift;
	return $class if ref($class);
	die "Los parametros de new deven ser pares clave => valor\n"
		unless (@_ % 2 == 0);
	my %defaults = (
		value => 'data',
	);
	my $obj = {
		%defaults,
		@_,
	};
	bless $obj, $class;
	# prevenimos la modificacion de los datos del objeto
	lock_hash %$obj;
}
1;

O con un constructor creado por el programador:

package Inmutable;
use strict;
use warnings;
use Hash::Util 'lock_hash';

sub new {
	my $class = shift;
	return $class if ref($class);
	die "Los parametros de new deven ser pares clave => valor\n"
		unless (@_ % 2 == 0);
	my %defaults = (
		value => 'data',
	);
	my $obj = {
		%defaults,
		@_,
	};
	bless $obj, $class;
	# prevenimos la modificacion de los datos del objeto
	lock_hash %$obj;
}

# funcion de acceso de solo lectura
sub value {
	my $self = shift;
	if (my $new_value = shift) {
		# al intentar establecer un valor
		die "Este objeto no puede ser modificado\n";
	} else {
		return $self->{value}
	}
}
1;

Python[editar]

En Python, algunos tipos primitivos(números, booleanos, cadenas, tuples, frozensets) son immutables, pero las clases creadas por el usuario son en general mutables. Para simular immutabilidad en una clase, se deben sobreescribir las características y borrado del atributo para activar excepciones:

class Immutable(object):
     """An immutable class with a single attribute 'value'."""
     def __setattr__(self, *args):
         raise TypeError("can't modify immutable instance")
     __delattr__ = __setattr__
     def __init__(self, value):
         # we can no longer use self.value = value to store the instance data
         # so we must explicitly call the superclass
         super(Immutable, self).__setattr__('value', value)

JavaScript[editar]

En JavaScript, algunos de los tipos integrados del lenguaje (números o cadenas de caracteres) son inmutables, pero las clases generadas por el programador son normalmente mutables. Para simular inmutabilidad en una clase, se deben establecer propiedades inmutables al prototipo del objeto:

/* clase Inmutable */

var Inmutable = function Inmutable (propiedades) {
    if (!(this instanceof Inmutable)) {
        return new Error (
            'Error[Inmutable.constructor]' +
            '::constructor' +
            ' llamado sin "new"'
        )
    }
    if (!(propiedades instanceof Object)) {
        return new Error (
            'Error[Inmutable.constructor]' +
            '::propiedades el objeto' +
            ' no es (' + propiedades +
            '), para ' + this
        )
    }
    var _empty_constructor =
          Inmutable._empty_constructor,
        tieneLaPropiedad = this.tieneLaPropiedad
    if (typeof _empty_constructor !== 'function') {
        return new Error (
            'Error[Inmutable.constructor]' +
            '::no se pudo encontrar un constructo vacio' +
            ' (' + _empty_constructor +
            ') en ' + Inmutable +
            ', para ' + this
        )
    }
    if (typeof tieneLaPropiedad !== 'function') {
        return new Error (
            'Error[Inmutable.constructor]' +
            '::no se pudo encontrar el metodo tieneLaPropiedad' +
            ' (' + tieneLaPropiedad + ') en ' + this
        )
    }
    var tiene_propiedad = false
    for (var propiedad in propiedades) {
        if (tieneLaPropiedad.call
              (propiedades, propiedad) !== true) {
            continue
        }
        if (tiene_propiedad !== true) {
            tiene_propiedad = true
        }
        this[propiedad] = propiedades[propiedad]
    }
    if (tiene_propiedad !== true) {
        return new Error (
            'Error[Inmutable.constructor]' +
            '::propiedades el objeto (' + propiedades +
            ') no tiene ninguna propiedad enumerable para ' + this
        )
    }
    _empty_constructor.prototype = this
    var Inmutable =
      new _empty_constructor
    _empty_constructor.prototype = null
    return Inmutable
}

Inmutable._empty_constructor =
    function Inmutable () {}

Racket[editar]

Racket difiere sustancialmente de otras implementaciones de Scheme ya que sus estructuras de datos son inmutables. Adicionalmente, proporciona datos mutables mediante mcons, mcar, set-mcar! etc. Además, soporta varios tipos inmutables, como cadenas de caracteres o vectores, que son usados habitualmente. Las nuevas estructuras son inmutables por defecto, a no ser que uno de sus campos o la estructura al completo sean declarados como mutables explícitamente:

(struct foo1 (x y)) ; todos los campos son inmutables
(struct foo2 (x [y #:mutable])) ; uno de los campos es mutable
(struct foo3 (x y) #:mutable) ; todos los campos son mutables

Este lenguaje también proporciona tablas Hash y diccionarios inmutables.

Scala[editar]

En Scala, cada variable puede ser definida como mutable o inmutable: si se usa la palabra clave val será una variable inmutable, mientras que usando var se declara una variable mutable.

val valorMaximo = 100
var valorActual = 1

El ejemplo anterior define un objeto inmutable valorMaximo (el tipo entero es deducido en tiempo de compilación) y otro objeto mutable valorActual.

Cabe destacar que aunque una variable inmutable no pueda ser reasignada, puede referenciar a un objeto mutable y sería posible llamar métodos mutables sobre dicha variable.

Por defecto, las clases colección son inmutables, por lo que los métodos que actualizan sus valores en realidad devuelven una nueva instancia en lugar de mutar la instancia existente. Este comportamiento puede parecer ineficiente, pero la implementación de estas clases y sus garantías de inmutabilidad permiten que la nueva instancia pueda reutilizar los nodos existentes, los cuales son muy eficientes creando copias.[5]

Copiar al escribir[editar]

Copiar al escribir (CAE), más conocida por su nombre en inglés (Copy-on-write COW), se trata de una técnica que aprovecha las ventajas de los objetos mutables e inmutables, es soportada por casi todo el hardware moderno. Usando esta técnica, cuando un usuario solicita copiar un objeto, el sistema creará en su lugar una referencia al objeto original. En el momento en que el usuario modifica el objeto a través de alguna de sus referencias, el sistema realizará una copia real (a la que aplicará el cambio solicitado) y acto seguido hará que la referencia apunte a este nuevo objeto. El resto de usuarios del sistema no se ven afectados por la operación, ya que aún referencian el objeto original. Por lo tanto, usando CAE, parece que todos los usuarios dispongan de una versión mutable de sus objetos, pero en caso de no modificar dichos objetos las ventajas en ahorro de espacio y mayor velocidad de los objetos inmutables se conservan. Esta técnica es popular en sistemas de memoria virtual ya que permite ahorrar memoria mientras que sigue permitiendo que las aplicaciones funcionen con normalidad.

Usos[editar]

Las Cadena de caracteres y otros objetos suelen ser expresados como objetos inmutables para mejorar su rendimiento en tiempo de ejecución en lenguajes de programación orientada a objetos. En Python, Java y el framework Microsoft .NET, las cadenas de caracteres son objetos inmutables. Tanto Java como el framework Microsoft.NET disponen de versiones mutables de cadenas de caracteres. En Java son StringBuffer y StringBuilder mientras que en.NET es StringBuilder. Python 3 ofrece una cadena de caracteres mutable llamada bytearray. Además, todos los envoltorios de tipos básicos en Java son inmutables.

Se puede forzar el cumplimiento de este patrón de diseño usando compiladores especializados (por ejemplo http://pec.dev.java.net/ (enlace roto disponible en Internet Archive; véase el historial, la primera versión y la última).), y existe una propuesta para añadir tipos inmutables a Java.

Otros patrones de diseño similares son la Interfaz inmutable y el Envoltorio inmutable.

En programación funcional pura no es posible crear objetos mutables, así pues, todos los objetos son inmutables.

Véase también[editar]

Referencias[editar]

Este artículo contiene material del libro El libro de los patrones de diseño de Perl

  1. a b c Goetz et al. Java Concurrency in Practice. Addison Wesley Professional, 2006, Section 3.4. Immutability
  2. David O'Meara (2003-04). «Objetos Mutables e Inmutables: Aseguraos de que los métodos no pueden ser sobreescritos.». Java Ranch. Consultado el 14 de mayo de 2012. «La mejor manera es declarar la clase como final. Esta práctica a veces es referida como "Inmutabilidad Fuerte". Esto evita que cualquiera pueda extender la clase y accidental o deliberadamente transformarla en mutable.» 
  3. http://javarevisited.blogspot.co.uk/2013/03/how-to-create-immutable-class-object-java-example-tutorial.html
  4. «Immutable objects». javapractices.com. Consultado el 15 de noviembre de 2012. 
  5. http://www.scala-lang.org/docu/files/collections-api/collections_12.html

Enlaces externos[editar]