Ya hemos visto que la herencia y
el polimorfismo eran dos de los pilares fundamentales en los que es apoya la
programación orientada a objetos. Pues bien, el tercero y último es la encapsulación, que es un mecanismo que
permite a los diseñadores de tipos de datos determinar qué miembros de los
tipos creen pueden ser utilizados por otros programadores y cuáles no. Las
principales ventajas que ello aporta son:
·
Se facilita a los programadores que vaya a usar el tipo
de dato (programadores clientes) el aprendizaje de cómo trabajar con él, pues
se le pueden ocultar todos los detalles relativos a su implementación interna y
sólo dejarle visibles aquellos que puedan usar con seguridad. Además, así se
les evita que cometan errores por manipular inadecuadamente miembros que no
deberían tocar.
·
Se facilita al creador del tipo la posterior modificación
del mismo, pues si los programadores clientes no pueden acceder a los miembros
no visibles, sus aplicaciones no se verán afectadas si éstos cambian o se
eliminan. Gracias a esto es posible crear inicialmente tipos de datos con un
diseño sencillo aunque poco eficiente, y si posteriormente es
necesariomodificarlos para aumentar su eficiencia, ello puede hacerse sin
afectar al código escrito en base a la no mejorada de tipo.
La encapsulación se consigue
añadiendo modificadores de acceso en
las definiciones de miembros y tipos de datos. Estos modificadores son
partículas que se les colocan delante para indicar desde qué códigos puede
accederse a ellos, entendiéndose por acceder el hecho de usar su nombre para
cualquier cosa que no sea definirlo, como llamarlo si es una función, leer o
escribir su valor si es un campo, crear objetos o heredar de él si es una
clase, etc.
Por defecto se considera que los
miembros de un tipo de dato sólo son accesibles desde código situado dentro de
la definición del mismo, aunque esto puede cambiarse precediendolos de uno los
siguientes modificadores (aunque algunos de ellos ya se han explicado a lo
largo del tema, aquí se recogen todos de manera detallada) al definirlos:
public:
Puede ser accedido desde cualquier código.
protected:
Desde una clase sólo puede accederse a miembros protected de objetos
de esa misma clase o de subclases suyas. Así, en el siguiente código las
instrucciones comentadas con // Error no son
válidas por lo escrito junto a ellas:
public class A
{
protected int x;
static void F(A a, B b, C c)
{
a.x = 1; // Ok
b.x = 1; // Ok
c.x = 1; // OK
}
}
public class B: A
{
static void F(A a, B b, C c)
{
//a.x = 1; // Error, ha de accederse a traves de objetos tipo B o C
b.x = 1; // Ok
c.x = 1; // Ok
}
}
public class C: B
{
static void F(A a, B b, C c)
{
//a.x = 1; // Error, ha de accederse a
traves de objetos tipo C
//b.x = 1; // Error, ha de accederse a
traves de objetos tipo C
c.x = 1; // Ok
}
}
Obviamente
siempre que se herede de una clase se tendrá total acceso en la clase hija –e
implíctiamente sin necesidad de usar la sintaxis <objeto>.<miembro>- a los miembros
que ésta herede de su clase padre, como muestra el siguiente ejemplo:
using System;
class A
{
protected int x=5;
}
class B:A
{
B()
{
Console.WriteLine(“Heredado
x={0} de clase A”, x);
}
public static void
Main()
{
new
B();
}
}
Como es de esperar,
la salida por pantalla del programa de ejemplo será:
Heredado x=5
de clase A
private:
Sólo puede ser accedido desde el código de la clase a la que pertenece. Es lo considerado por defecto.
internal:
Sólo puede ser accedido desde código perteneciente al ensamblado en que se ha
definido.
protected
internal: Sólo puede ser accedido desde código perteneciente al
ensamblado en que se ha definido o desde
clases que deriven de la clase donde se ha definido.
Es importante recordar que toda
redefinición de un método virtual o abstracto ha de realizarse manteniendo los
mismos modificadores que tuviese el método original. Es decir, no podemos
redefinir un método protegido cambiando su accesibilidad por pública, pues si
el creador de la clase base lo definió así por algo sería.
Respecto a los tipos de datos,
por defecto se considera que son accesibles sólo desde el mismo ensamblado en
que ha sido definidos, aunque también es posible modificar esta consideración
anteponiendo uno de los siguientes modificadores a su definición:
public:
Es posible acceder a la clase desde cualquier ensamblado.
internal:
Sólo es posible acceder a la clase desde el ensamblado donde se declaró. Es lo
considerado por defecto.
También pueden definirse tipos
dentro de otros (tipos internos) En
ese caso serán considerados miembros del tipo contenedor dentro de la que se
hayan definido, por lo que les serán aplicables todos los modificadores válidos
para miembros y por defecto se considerará que, como con cualquier miembro, son
privados. Para acceder a estos tipos desde código externo a su tipo contenedor
(ya sea para heredar de ellos, crear objetos suyos o acceder a sus miembros
estáticos), además de necesitarse los permisos de acceso necesarios según el
modificador de accesibilidad al definirlos, hay que usar la notación <nombreTipoContendera>.<nombreTipoInterno> como muestra este ejemplo:
class A // No lleva modificador, luego se considera
que es internal
{
public
class AInterna {} // Si ahora no se pusiese public se consideraría
private
}
class B:A.AInterna //
B deriva de la clase interna AInterna definida dentro de A. Es
{} // válido porque A.AInterna es
pública