Clases
Una clase es la plantilla a partir de la
cual es posible crear objetos. Todos los objetos de la misma clase comparten la
interface (es decir, métodos, campos y propiedades), pero los datos que
contiene cada objeto en sus campos y propiedades pueden diferir.
Por su misión específica, las clases (y las estructuras) se pueden
dividir en tres grandes grupos:
Clases que ofrecen un valor
Son aquellas en las que lo principal es el
valor que contienen. Por ejemplo, para pagar un artículo de 50 €. Es posible usar
un billete de 50 u otro igual, también de 50. O incluso cinco de 10 € Lo
importante es pagar los 50 €, pero no los billetes que utilicemos. Los objetos
cuyo único interés es su valor son intercambiables, y suelen estar
implementados como estructuras (Int32, Int16, Double...).
Clases que ofrecen un servicio
Aquí estarían las clases cuyo único
interés es el servicio que ofrecen. Por ejemplo, para comprar el pan, lo más
importante es que alguien nos lo venda, pero ese quién nos da igual. Nos
interesa el servicio, pero no quién haga ese servicio. Un ejemplo de clase que
ofrece un servicio es la clase Console, pues no se usan nunca instancias de
ella (de hecho, no se puede instanciar), sino que, usamos sus métodos static
para aprovechar los servicios que nos ofrece.
Clases de identidad
Aquí, lo más relevante no es la
información que contiene ni el servicio que prestan, sino cuál es el objeto que
nos ofrece sus datos y servicios. Por ejemplo, en una factura, lo más importante
es la factura en sí. No es lo mismo la factura del gas del mes pasado que la de
este mes, o la del teléfono de hace dos años. Por esto decimos que son tipos de
identidad, porque lo más relevante es el objeto en sí.
Puede haber clases que, según los miembros
que se examinen, se incluyan en dos grupos, o, incluso, en los tres. Por
ejemplo, una clase tipo TarjetaDeCrédito sería, claramente, un tipo de
identidad. Pero también ofrece un
servicio que no depende de la identidad del objeto, como calcular la tasa anual
que cobra una determinada entidad por ella.
Herencia
Gracias a la herencia podemos definir
clases nuevas basadas en clases antiguas, añadiéndoles más datos o
funcionalidad. La relación de herencia debe basarse en una relación jerárquica
de conjuntos y subconjuntos más pequeños incluidos en aquellos. Algunas de las
clases derivadas pueden añadir algo específico de ella que no tuviera sentido
en otro subconjunto distinto. Tomemos por ejemplo un dispositivo de
reproducción de vídeo. Pero un reproductor de VHS no es igual que un
reproductor de DVD, a pesar de que ambos son subconjuntos de un conjunto mayor;
dispositivos de reproducción de video. Podemos establecer una clase base que
determine e implemente los miembros y el comportamiento o una parte del
comportamiento común de todos los dispositivos de reproducción de vídeo (reproducir,
parar, pausa, expulsar...), y esta clase servirá de base a las clases de
reproductor VHS y reproductor de DVD, que derivarán sus miembros de la clase
base. Sin embargo, alguna de las clases derivadas puede añadir algo específico
de ella que no tuviera sentido en otro subconjunto distinto. Por ejemplo, la
clase del reproductor de VHS necesitará incluir también un método para
rebobinar la cinta, cosa que no tendría sentido con un reproductor de DVD.
Nota: La instrucción Enum
La instrucción enum sirve para agrupar
constantes. En este caso, se han agrupado cuatro constantes (Parado, EnPausa,
Reproduciendo y SinMedio) en un grupo llamado EstadoReproductor, de forma que es
posible utilizar un código mucho más fácil de leer que usando números
directamente.
public enum EstadoReproductor
{
Parado,
EnPausa,
Reproduciendo,
SinMedio
}
class DispositivoVideo
{
protected bool reproduciendo=false;
protected bool pausado=false;
protected bool medio=false;
public virtual void Reproducir()
{
if (!medio)
Console.WriteLine("Inserte
un medio");
else if (reproduciendo)
Console.WriteLine("Ya
estaba reproduciendo");
else
{
Console.WriteLine("Reproduciendo
vídeo");
reproduciendo=true;
}
}
public virtual void Detener()
{
if (!medio)
Console.WriteLine("Inserte
un medio");
else if (reproduciendo)
{
Console.WriteLine("Reproducción
detenida");
reproduciendo=false;
pausado=false;
}
else
Console.WriteLine("Ya
estaba parado");
}
public virtual void Pausa()
{
if (!medio)
Console.WriteLine("Inserte
un medio");
else if (reproduciendo && !pausado)
{
Console.WriteLine("Reproducción
en pausa");
pausado=true;
}
else if(reproduciendo && pausado)
{
Console.WriteLine("Reproducción
reanudada");
pausado=false;
}
else
Console.WriteLine("No se
puede pausar. Está parado");
}
public virtual void IntroducirMedio()
{
if (medio)
Console.WriteLine("Antes
debe expulsar el medio actual");
else
{
Console.WriteLine("Medio
introducido");
medio=true;
}
}
public virtual void Expulsar()
{
if (!medio)
Console.WriteLine("Inserte
un medio");
else
{
medio=false;
reproduciendo=false;
pausado=false;
Console.WriteLine("Expulsando
medio");
}
}
public EstadoReproductor Estado
{
get
{
if (!medio)
return EstadoReproductor.SinMedio;
else if (pausado)
return EstadoReproductor.EnPausa;
else if (reproduciendo)
return EstadoReproductor.Reproduciendo;
else
return EstadoReproductor.Parado;
}
}
}
Todos los dispositivos de vídeo incorporan
el comportamiento expuesto en el código de arriba. Esta clase incorpora todo
aquello que, como mínimo, necesita un dispositivo de reproducción de vídeo. Podemos
utilizar esta clase como base para construir otras clases de reproductores de
vídeo más específicas, como un reproductor de VHS o un reproductor de DVD. Hay
tres campos con el modificador de acceso protected, este identificador hace que
el miembro en cuestión sea visible en las clases derivadas, pero no en el
cliente. De este modo, estos tres campos estarán disponibles en cualquier clase
derivada de la clase DispositivoVideo. También se añade la palabra virtual en
la declaración de cada método para permitir que las clases derivadas puedan
modificar la implementación de estos métodos. Si no lo hacen, se ejecutará el
código de la clase base. Se escriben a continuación las clases derivadas:
class DispositivoVHS:DispositivoVideo
{
public void Rebobinar()
{
if (!medio)
Console.WriteLine("Introduzca
una cinta");
else
Console.WriteLine("Cinta rebobinada");
}
public override void IntroducirMedio()
{
if (medio)
Console.WriteLine("Antes
debe expulsar la cinta actual");
else
{
Console.WriteLine("Cinta introducida");
medio=true;
}
}
}
Esta clase es mucho más corta, pues sólo tiene
que añadir o modificar las partes de la clase base que no sirven para un
reproductor específico de VHS. Para indicar que queremos heredar los miembros de
una clase se añaden dos puntos al final del nombre de la clase y se pone a
continuación el nombre de la clase base. En la clase DispositivoVHS se ha añadido
un método llamado Rebobinar que no existe en la clase base y se ha sobreescrito
el método IntroducirMedio que sí se encontraba en la clase base. Para sobreescribir
un método virtual de la clase base hay que utilizar la palabra override, El
resto de miembros de la clase base no es necesario tocarlos.
En cuanto a la implementación de la clase DispositivoDVD:
class DispositivoDVD:DispositivoVideo
{
public void IrA(int escena)
{
if (!medio)
Console.WriteLine("Inserte
un medio");
else
{
reproduciendo=true;
pausado=true;
}
}
}
No se ha tocado ningún método de la clase
base, y se ha añadido el método IrA, para saltar a una escena determinada. La
herencia ayuda mucho porque no es necesario repetir el mismo código una y otra
vez. A continuación un pequeño programa que utiliza las clases implementadas
más arriba:
class Videos
{
static void Main()
{
Console.WriteLine("Crea un dispositivo genérico");
DispositivoVideo video=new DispositivoVideo();
Console.WriteLine();
Acciones(video);
Console.WriteLine();
Console.WriteLine("Crea un reproductor VHS");
video=new DispositivoVHS();
Acciones(video);
Console.WriteLine();
Console.WriteLine("Crea reproductor DVD");
video=new DispositivoDVD();
Acciones(video);
Console.ReadLine();
}
static void Acciones(DispositivoVideo video)
{
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar IntroducirMedio");
Console.ReadLine();
video.IntroducirMedio();
Estado(video);
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar Reproducir");
Console.ReadLine();
video.Reproducir();
Estado(video);
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar Pausa");
Console.ReadLine();
video.Pausa();
Estado(video);
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar Reproducir");
Console.ReadLine();
video.Reproducir();
Estado(video);
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar Pausa");
Console.ReadLine();
video.Pausa();
Estado(video);
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar Detener");
Console.ReadLine();
video.Detener();
Estado(video);
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar Pausa");
Console.ReadLine();
video.Pausa();
Estado(video);
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar Expulsar");
Console.ReadLine();
video.Expulsar();
Estado(video);
if (video is DispositivoVHS)
{
DispositivoVHS
videoVHS=(DispositivoVHS) video;
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar Rebobinar");
Console.ReadLine();
videoVHS.Rebobinar();
Estado(video);
}
if (video is DispositivoDVD)
{
DispositivoDVD
videoDVD=(DispositivoDVD) video;
Console.WriteLine();
Console.WriteLine("Pulsa
intro para invocar IrA(5)");
Console.ReadLine();
videoDVD.IrA(5);
Estado(video);
}
Console.WriteLine();
}
static void Estado(DispositivoVideo video)
{
Console.WriteLine("Estado
actual del reproductor: {0}",
video.Estado);
}
}
En el método main se ha utilizado el
objeto video, declarado de la clase DispositivoVideo para instanciar los
dispositivos de los tres tipos distintos. Y se les pasa a los métodos Acciones
y Estado, cuando estos solamente aceptan como argumento un objeto de la clase
DispositivoVideo. Esto funciona bien porque los dispositivos VHS y DVD son
también dispositivos de vídeo, pues al haber derivado estas dos clases de la clase
DispositivoVideo, el compilador entiende esto mismo, de modo que los admite sin
ningún tipo de problemas. Por otro lado, los métodos Rebobinar e IrA no están
implementados en la clase DispositivoVideo, sino que son particulares de las
otras dos clases. Por este motivo se realiza la conversión al tipo específico
que implementa el método para poder invocarlo. Es lo que se ha remarcado en
negrita al final del programa.
Al invocar el método IntroducirMedio una
segunda vez, cuando el objeto es un reproductor de VHS. Se ejecutó el método
sobre-escrito en la clase DispositivoVHS, en lugar de ejecutarse el método
virtual definido en la clase base.
La herencia permite reemplazar métodos de
la clase base con toda tranquilidad, porque siempre se ejecutarán correctamente.
El polimorfismo es la capacidad que tienen los objetos de comportarse de un
modo distinto unos de otros aun compartiendo los mismos miembros.
No hay comentarios:
Publicar un comentario