Sie sind auf Seite 1von 23

Programación Orientada a Objetos en .

NET
Introducción:
La programación orientada a objetos (POO) nos permite escribir código menos propenso a fallos
además de permitirnos la reutilización de código de forma más conveniente.
En este artículo veremos las características de la POO desde el punto de vista de los lenguajes de .NET
Framework y cómo utilizar los distintos elementos que nos permitirán crear código que sea más fácil de
escribir y mantener.

LA PROGRAMACIÓN ORIENTADA A OBJETOS


En Todo Programación existe una sección denominada Cuadernos de Principiantes donde se estudia
algoritmia y estructuras de datos a nivel iniciación. Está planificado que se estudie a nivel teórico la
programación orientada a objetos, por tanto para aquellos que no tengáis noción alguna sobre POO
mejor guarda a buen recaudo este número de TP y espera a aprender los conceptos teóricos necesarios
para luego aplicarlos en el marco, nunca mejor dicho, de .NET.

LOS PILARES DE LA POO


Recordemos que tres son las principales características de un lenguaje orientado a objetos, es decir, se
considera que un lenguaje está totalmente orientado a objetos si es capaz de proveer estas tres
características:
• Encapsulación
• Herencia
• Polimorfismo
Veamos una pequeña descripción de cada una de ellas y después las ampliaremos para comprender
mejor su significado y cómo puede ayudarnos a crear aplicaciones que aprovechen todas las
posibilidades que nos da la POO.
• La ENCAPSULACIÓN es la cualidad de unificar los datos y la forma de manipularlos, de esta
forma podemos ocultar el funcionamiento de una clase y exponer solo los datos que manipula
(mediante propiedades), así como proveer de medios para poder manipular dichos datos (mediante
métodos). De esta forma solo exponemos al mundo exterior la información y la forma de manipularla,
ocultando los detalles usados para manejar esos datos y, lo que es más importante, evitando que nadie
manipule de una forma no controlada dicha información.
• La HERENCIA es la cualidad de poder crear nuevas clases (o tipos) basadas en otras clases, de
forma que la nueva clase obtenga todas las características de la clase que ha heredado, tanto los datos
que contiene como la forma de manipularlos, pudiendo añadir nuevas características e incluso cambiar
el comportamiento de algunas de las incluidas en la clase base, (siempre que así se haya previsto).
Mediante la herencia podemos crear de forma fácil una jerarquía de clases que comparten un mismo
comportamiento básico pero que cada nueva generación puede tener (y de hecho tiene) un nuevo
comportamiento.
• El POLIMORFISMO es la cualidad de implementar de forma particular algunas de las
características que tienen las clases, de forma que cuando necesitemos usarlas no nos preocupe la
implementación interna que cada una tenga, lo que realmente nos interesa o nos debe importar es que
podemos usar esas características e incluso podamos acceder a ellas de forma anónima... o casi.

OTROS CONCEPTOS DE LA POO


Tal como tendrás oportunidad de ver en los Cuadernos de Principiantes y lo indicado en el cuadro Los
pilares de la POO, la POO se basa en tres características que son comunes a todos los lenguajes
orientados a objetos, pero si tenemos esas características y no sabemos cómo aplicarlas, la verdad es
que no nos será de mucha utilidad.
Pero antes de ver algo de código concreto, creo que es importante que aprendamos otros conceptos
relacionados también con la POO, pero esta vez desde un punto de vista del programador, es decir,
vamos a dejar en parte la teoría y vamos a ser algo más prácticos, ya que los siguientes conceptos serán
con los que tendremos que "bregar" a diario. Además nos interesa conocerlos para aprovechar lo que un
lenguaje de programación orientado a objetos nos ofrece, si bien, es posible que, al menos de forma
genérica, no todos los lenguajes dispongan de ellos.
Por eso, aunque lo que se ha dicho y se diga a continuación será válido para cualquier lenguaje
orientado a objetos, lo vamos a enfocar desde el punto de vista de .NET Framework, más
concretamente desde el punto de vista del programador de Visual Basic .NET y C#.

LAS CLASES Y ESTRUCTURAS


Como hemos estado mencionando, en los lenguajes orientados a objetos, existe el concepto clase.
Cuando hablamos de clases, también podemos extenderlo a estructuras, de hecho, para los
programadores de C++ una clase no es más que una estructura que se comporta de forma diferente.
Una clase es una pieza de código en la que podemos definir una serie de datos y al mismo tiempo unos
métodos (funciones o procedimientos) que nos permitirán acceder a esos datos.
Cuando definimos una clase, lo que estamos haciendo es definir una plantilla, a partir de la cual
podemos crear objetos en la memoria. Por tanto, la clase es el molde con el cual podemos crear nuevos
objetos. Para poder crear algo "tangible" a partir de una clase, tenemos que crear en la memoria un
nuevo objeto del tipo de la clase, en estos casos lo que decimos es que instanciamos un nuevo objeto de
la clase. A partir de ese momento tendremos algo real con lo que podemos trabajar: una instancia de la
clase, es decir, la definición realizada en la clase se ha convertido en un objeto al que podemos acceder
y que podemos empezar a utilizar, dándole nuevos valores a los datos que manipula y usando las
funciones que nos permiten manipular dichos datos.
La diferencia principal entre una clase y una estructura es la forma en que se crean los objetos que
representan a esas "ideas". Los objetos creados a partir de las clases son objetos por referencia, es decir,
si declaramos una variable para manipular ese objeto, lo que tendremos será una referencia (o puntero)
a una dirección de memoria en la que realmente está el objeto. Mientras que los objetos creados a partir
de una estructura se almacenan de forma diferente, en lugar de "apuntar" a una dirección de memoria
en la que se encuentra el objeto, es como si las variables declaradas como estructuras fuesen realmente
el objeto permitiéndonos hacer ciertas operaciones y manipulaciones que los objetos obtenidos a partir
de una clase no pueden realizar de la misma forma. Esto lo veremos después con más detalle.
NOTA: Clases
En .NET siempre usamos una clase para escribir cualquier tipo de código. Por tanto, hagamos lo que
hagamos en .NET Framework, debemos hacerlo dentro de una clase. Esto no quiere decir que siempre
tengamos que usar las características de la POO, ya que si simplemente queremos hacer una aplicación
que muestre un mensaje en la consola, el código no tiene porqué usar la herencia, el polimorfismo o la
encapsulación, simplemente escribimos el código que muestre el mensaje y asunto arreglado, pero lo
que si podremos hacer es usar algunas de las "otras" ventajas que nos aporta la programación orienta a
objetos.

INTERFACES
Cuando hablamos de polimorfismo, ineludiblemente tenemos que hablar de las interfaces, ya que,
principalmente, nos posibilita utilizar esta característica de la POO. La pregunta es: ¿qué es una
interfaz? Aquí no hablamos de "interfaces de usuario", es decir, lo que se mostrará al usuario de nuestra
aplicación, sino a una clase especial en la que solamente se definen los métodos y propiedades que una
clase que la implemente debe codificar. Las interfaces representan un contrato, de forma que cualquier
clase que la implemente debe utilizar los miembros de la interfaz usando la misma forma en que ésta la
ha descrito: mismo número de argumentos, mismo tipo de datos devuelto, etc.
Gracias a la implementación de interfaces podemos crear relaciones entre clases que no estén derivadas
de la misma clase base, pero que tengan métodos comunes, al menos en la forma, aunque no
necesariamente en el fondo. Anteriormente usamos el ejemplo del método Guardar, este método se
puede definir en una interfaz, las clases que quieran implementar un método Guardar "estandarizado"
firmarán un contrato con la interfaz que lo especifica, aunque la forma interna de funcionamiento solo
atañe al programador de la clase, lo importante es saber que cualquier clase que haya firmado ese
contrato tendrá que seguir las condiciones impuestas por la interfaz, de esta forma todas las clases
tendrán un método Guardar "compatible", aunque, tal como mostramos antes, cómo se realice esa
acción de guardar no debe preocuparnos, simplemente nos fiaremos de que se ha implementado
adecuadamente para almacenar los datos que la clase manipula.
NOTA: HERENCIA MÚLTIPLE Y HERENCIA SIMPLE
En C++ y algunos otros lenguajes orientados a objetos se permiten la herencia múltiple, es decir, una
clase se puede derivar de varias clases a la vez. Los lenguajes de .NET Framework, usan lo que se
denomina herencia simple, es decir, una clase solo se puede derivarse directamente de otra clase, si
bien se permite implementar múltiples interfaces.
Pero debido a cómo funciona la herencia, cualquier clase derivada a partir de otra, heredará
indirectamente todas las clases e interfaces que la clase base haya heredado o declarado. Además, en
.NET, todas las clases siempre se derivan de la clase base Object que es la clase que está en la parte
superior de la jerarquía de clases.

CONSTRUCTORES Y DESTRUCTORES, EL PUNTO DE INICIO Y FINAL DE


LAS CLASES
Cuando creamos un objeto a partir de una clase, se sigue un proceso, el cual empieza en el momento en
que decidimos crear una nueva instancia de dicha clase.
En estos casos, el compilador utiliza lo que se llama el constructor de la clase. Siempre que se crea un
nuevo objeto en la memoria está involucrado el constructor de la clase.
Los constructores son procedimientos especiales (funciones que no devuelven un valor) en los que
podemos escribir toda la lógica que debe usarse para la correcta creación del objeto. Por ejemplo,
podemos inicializar las variables usadas, podemos asignarle algunos valores predeterminados, etc.
De igual forma, cuando un objeto ya no se necesita más, se destruye mediante una llamada al destructor
de la clase. En .NET la destrucción de los objetos suele hacerse de forma automatizada, es decir, a
diferencia de lo que ocurre en otros entornos de programación, no es necesario destruir explícitamente
un objeto para eliminarlo de la memoria, esa gestión de limpieza de objetos la realiza el recolector de
basura (Garbage Collector, GC) de .NET, el cual decide cuando un objeto no se necesita más y en ese
caso lo elimina dejando libre la memoria utilizada para otros menesteres.

SOBRECARGA (OVERLOAD)
Una de las características que también nos ofrece los lenguajes orientados a objetos es la posibilidad de
definir varias funciones de las clases con un mismo nombre, de esta forma, podremos crear versiones
diferentes, por ejemplo para que reciban argumentos de distintos tipos sin necesidad de cambiarle el
nombre.
Supongamos que queremos hacer una función que realice cualquier tipo de operación sobre dos valores
numéricos, sería lógico pensar que si esos valores son de tipo entero, el resultado que devuelva la
función también debería ser de tipo entero, en caso de que los valores a usar en la operación son de tipo
flotante, el resultado podría devolverlo de ese mismo tipo.
En los lenguajes no orientado a objetos, tendríamos que crear dos funciones con nombres diferentes,
por ejemplo: sumaInt y sumaFloat. Pero la sobrecarga nos permite crear dos funciones que se llamen
suma y el compilador utilizará la adecuada según el tipo de datos que pasemos como argumentos.
El único requisito para poder crear sobrecargas de métodos es que las diferentes versiones se
diferencien en los argumentos, ya sea porque sean de diferentes tipos de datos o porque el número de
argumentos usados sea diferente, de esa forma el compilador no tendrá ningún problema en saber cual
debe usar en cada ocasión. La sobrecarga la podemos aplicar tanto a los constructores como a cualquier
otro método de la clase.
NOTA: Sobrecarga
No existirá la posibilidad de crear métodos sobrecargados si solamente se diferencian en el tipo de
datos devuelto, ya que en esos casos el compilador no podrá decidir correctamente qué método debe
utilizar.

LOS MIEMBROS DE LAS CLASES: CAMPOS, PROPIEDADES Y MÉTODOS


Como hemos comentado, las clases manejan datos y proveen de funciones para acceder a esos datos.
Para ser precisos, los datos se mantienen o almacenan internamente en los campos declarados en las
clases. Los campos no son otra cosa que variables declaradas en la clase, habitualmente declaradas de
forma privada. ¿Por qué declaradas de forma privada? Precisamente para seguir o cumplir la
característica de encapsulación de la POO, es decir, los datos no deben exponerse de forma directa.
Si queremos exponer los datos, podemos usar las propiedades. Las propiedades son funciones
especiales que nos permiten acceder a esos datos, aunque para ser más precisos, las propiedades
realmente representan a los datos que una clase contiene, al menos de forma pública. De esa forma
podemos "controlar" la forma en que se leen o asignan esos datos, ya que las propiedades realmente
son funciones en las que podemos escribir código para controlar los valores asignados o leídos.
Los métodos nos permitirán realizar acciones sobre los datos, por ejemplo devolver un rango de valores
o simplemente una representación amigable de la información contenida. Debido a que algunas veces
los métodos devolverán algo y otras no, podemos usar tanto funciones que devuelvan o no un valor.
NOTA: Métodos
En C# los métodos siempre son funciones, que devolverán un tipo concreto o el valor especial void,
que se usa para indicar que una función no devolverá ningún valor.
En Visual Basic .NET existen dos tipos de métodos distintos, las funciones (Function) que siempre
devuelven un valor y los procedimientos (Sub) que no devuelven ningún valor.

Además de los campos, métodos y propiedades, las clases tienen otros miembros como los eventos y
las enumeraciones. Éstos nos permitirán recibir notificaciones de cuando algo ocurra (eventos) o
declarar ciertos valores constantes que podemos usar para restringir algunos valores asignados a las
propiedades o que nos permitan seleccionar de forma coherente la información que queremos obtener
(enumeraciones).

EL ÁMBITO DE LOS MIEMBROS DE LAS CLASES


Las buenas formas de trabajar con las clases nos indican que los campos deberían ser privados, con
idea de que no estén accesibles de forma externa. Por supuesto también podemos definir otros
miembros de las clases de forma privada, esto es útil cuando la funcionalidad es para uso exclusivo de
otros miembros de la clase. Pero cuando queremos exponer la funcionalidad fuera de la clase podemos
hacerla de varias formas, aquí es donde entran en juego el ámbito de los miembros de las clases.
El ámbito lo aplicamos para permitir el acceso desde cualquier código fuera de la clase o para restringir
ese acceso. Dependiendo de cómo queramos que se acceda a los miembros de la clase podemos usar
distintos modificadores de ámbito.
Veamos los que podemos usar y cuando y porqué usarlos.
La instrucción entre paréntesis será la que tendremos que usar en C#.
• Private (private). Para uso privado. Cuando declaramos un miembro como privado sólo lo
podremos acceder desde la propia clase. Este es el más restrictivo y el que se recomienda para
los campos y las funciones de uso interno.
• Protected (protected). Uso protegido. Los elementos declarados como protegidos sólo estarán
accesibles, además de en la propia clase, por cualquier clase derivada.
• Friend (internal). Para uso dentro de la propia aplicación. Cuando declaramos un miembro con
este modificador, solo podremos acceder a él desde la propia clase o desde cualquier código que
se encuentre en el mismo ensamblado (proyecto).
• Protected Friend (protected internal). Una mezcla de Protected y Friend, es decir solo
accesible desde las clases derivadas o desde el mismo proyecto.
• Public (public). Este modificador de ámbito nos permite exponer públicamente cualquier
miembro de la clase, de forma que no haya restricciones para acceder a él.
NOTA: Ámbito
Los miembros de una clase los podemos declarar sin especificar el ámbito, dependiendo del lenguaje de
programación que usemos se aplicará un modificador de ámbito u otro. En C#, si no indicamos el
ámbito, las declaraciones se consideran privadas, mientras que en Visual Basic .NET el ámbito
predeterminado es Friend.
MIEMBROS VIRTUALES, NO REEMPLAZABLES Y ABSTRACTOS
Para ir terminando la parte "teórica" sobre la programación orientada a objetos, veamos cómo podemos
darle un significado distinto a los miembros de una clase, dependiendo de cómo queramos que se
comporten y por extensión cómo podemos utilizarlos tanto en la propia clase como en las clases
derivadas.
Como hemos comentado, cuando una clase hereda a otra podemos modificar el comportamiento de los
miembros heredados, pero estos solamente se podrán modificar si la clase base así lo contempla o lo
permite. De forma predeterminada, al menos en .NET, cuando declaramos un método o una propiedad
en una clase, solo podremos acceder a él desde una instancia creada (un objeto) en memoria, desde
donde podemos usarlos dependerá del ámbito que le hayamos aplicado. De igual forma, el que una
clase que se base en otra, pueda crear su propia versión de ese método o propiedad dependerá de que la
clase base lo haya declarado como virtual (Overridable en VB .NET). Los métodos virtuales serán los
que podamos sobrescribir en las clases derivadas, de forma que podamos crear nuestras propias
versiones. En .NET los miembros de una clase no son virtuales de forma predeterminada. Por tanto, si
queremos que la clase derivada pueda crear su propia versión de un método, debemos declararlo como
virtual o "redefinible".
Si en una clase base hemos definido un método virtual, pero posteriormente queremos que no se pueda
seguir redefiniendo en otras clases derivadas, debemos indicarlo usando el modificador
NotOverridable, el cual se usará junto con Overrides, ya que sobrescribe un miembro de la clase base
y como además lo queremos marcar como no virtual, debemos usar las dos instrucciones: Overrides
NotOverridable, (en C# se indicará con override sealed).
Pero también se nos puede presentar el caso contrario, en el que queremos que un método forzosamente
haya que redefinirlo en las clases derivadas, en esos casos la clase base que lo define no incluye
ninguna implementación, es decir, el método no contiene código ejecutable, solo la definición, (como
ocurre con las interfaces). Se dice que estos métodos son abstractos porque solo se ha definido en la
forma y no se ha implementado ningún código ejecutable. En Visual Basic se definen usando el
modificador MustOverride (asbtract en C#). Estos métodos abstractos solo se pueden declarar en
clases abstractas (MustInherit en Visual Basic, abstract en C#) y por la necesidad de tener que
redefinirlos, son implícitamente virtuales.
Las instrucciones o modificadores que nos permiten crear estos tipos de miembros son:
• Overridable (virtual). Los miembros virtuales son los que las clases derivadas puedes
sobrescribir para crear su propia versión. Para indicar en una clase derivada que estamos
sobrescribiendo dicho elemento, usaremos Overrides (override en C#).
• NotOverridable (sealed). Los miembros virtuales heredados se pueden marcar como no
virtuales, (las siguientes clases derivadas no podrán sobrescribirlos), usando esta instrucción a
continuación de Overrides (override).
• MustOverride (abstract). Un método que se debe reemplazar en la clase derivada y que solo
se define en la clase base sin ningún código ejecutable. Los métodos abstractos son virtuales por
defecto y solo se pueden declarar en clases abstractas.

MIEMBROS DE INSTANCIAS Y COMPARTIDOS


En todos estos casos, los miembros de la clase siempre son miembros de instancia, es decir, solo
existen en la memoria cuando se crea un nuevo objeto (se crea una nueva instancia). Pero es posible
que nos interese crear miembros compartidos, es decir, miembros que pertenecen a la propia clase, no a
ninguna instancia en particular. Dándonos la oportunidad de poder acceder siempre a ellos,
independientemente de que hayamos creado o no un nuevo objeto en la memoria. En estos casos
decimos que creamos miembros compartidos (estáticos en el argot de C#/C++), esta diferencia de
"nomenclatura" dependiendo del lenguaje de programación, es porque para definir un miembro
perteneciente a la clase y no a una instancia en particular, usaremos en Visual Basic la instrucción
Shared (compartido), mientras que en C# se usará la instrucción static (estático).
Resumiendo, Shared (static), declara un miembro compartido, los miembros compartidos no
pertenecen a ninguna instancia en particular y solamente pueden acceder a campos u otros miembros
también compartidos. Desde los miembros de instancia podemos acceder tanto a miembros
compartidos como de instancia.
NOTA: STATIC
En Visual Basic existe también la instrucción Static, (que no tiene equivalencia en C#), en este caso se
utiliza con variables declaradas en un procedimiento y sirven para indicar que esa variable debe
mantener el valor entre distintas llamadas a dicho procedimiento, a diferencia del resto de variables que
solo existen mientras se ejecuta el código del procedimiento y cuyos valores se pierden al finaliza la
ejecución del mismo.

CLASES ABSTRACTAS Y SELLADAS


De igual forma que podemos modificar el comportamiento de los miembros de una clase, también
podemos cambiar el comportamiento predeterminado de las clases.
Como hemos comentado, las clases de .NET pueden usarse para crear nuevas clases derivadas de ellas,
esta es la funcionalidad predeterminada, pero no obligatoria, es decir, si queremos podemos usar una
clase por si misma o como base de otras.
Pero también podemos hacer que una clase solamente se use como clase base de otras, pero no se
puedan usar para crear nuevas instancias en memoria, este es el caso de las clases abstractas. Una clase
abstracta puede contener miembros abstractos, miembros normales o virtuales. Para indicar que una
clase es abstracta, se usa el modificador MustInherit en Visual Basic o abstract en C#.
La contrapartida de las clases abstractas son las clases selladas o clases que no se pueden usar como
clases base, en estos casos las clases las definiremos como NotInheritable en Visual Basic o sealed en
C#. Como es lógico, las clases no heredables se pueden usar en ocasiones en las que no nos interese
que nadie cambie el comportamiento que tiene, por tanto no se podrán declarar miembros virtuales ni
abstractos, ya que no tendría ningún sentido.
Las estructuras siempre son "clases selladas", (aunque no se use un modificador para indicarlo), por
tanto, no podemos usarlas como base de otras.

VISUAL BASIC .NET Y LA POO


Tal como comentamos en el artículo anterior, uno de los pilares de la POO es la herencia. Mediante la
herencia podemos definir clases totalmente operativas y crear nuevas clases basadas en ellas de forma
que hereden toda la funcionalidad de la clase base y nos permita ampliarla. Por tanto, vamos a empezar
viendo cómo definir una clase y cómo aplicar en Visual Basic .NET el resto de conceptos relacionados
con la programación orientada a objetos.
DEFINIR CLASES EN VISUAL BASIC .NET
Antes de poder usar las características de la POO tendremos primero que aprender a declarar una clase.
La declaración de una clase se define usando la instrucción Class seguida del nombre de la clase y
termina usando las instrucciones End Class.
Dentro de ese bloque definiremos los campos, propiedades y métodos que queramos que tenga la clase.

NOTA:
En Visual Basic .NET la definición de una clase se puede hacer en cualquier fichero de código (con
extensión .vb), aunque no es obligatorio hacerlo en un fichero independiente como ocurría con las
versiones anteriores, es recomendable hacerlo, para que nos resulte más fácil de mantener.

Public Class A
Private _prop2 As Integer
Private _prop1 As String
'
Public Property Prop1() As String
Get
Return _prop1
End Get
Set(ByVal value As String)
If value <> "" Then
_prop1 = value
End If
End Set
End Property
Public Property Prop2() As Integer
Get
Return _prop2
End Get
Set(ByVal value As Integer)
_prop2 = value
End Set
End Property
'
Public Sub Mostrar()
Console.WriteLine("{0}, {1}", _prop1, _prop2)
End Sub
End Class

Tal como podemos ver en el listado 1, tenemos una clase llamada A que define dos campos, dos
propiedades y un método. Los dos campos, declarados como privados, se usan para mantener
"internamente" la información que se expone mediante las dos propiedades públicas, de esta forma
protegemos los datos y esta sería una forma de encapsular la información.
De las dos propiedades definidas para acceder a esos datos, solo la propiedad Prop1 hace una
comprobación de que no se asigne una cadena vacía al campo que mantiene internamente la
información, aunque en este ejemplo por su simplicidad no hacemos más comprobaciones, en una clase
algo más compleja, se podrían realizar otras comprobaciones, por ejemplo si el valor a almacenar es
una cuenta de email, podríamos comprobar que es una cadena correctamente formada.
Las propiedades suelen definir dos bloques de código, uno, el bloque Get se utiliza cuando queremos
acceder al valor devuelto por la propiedad, el otro es el bloque Set, el cual se utilizará cuando
asignemos un valor a la propiedad.
El método Mostrar se usará para mostrar el contenido de las dos propiedades por la consola y está
definido como Sub (void en C#) porque no devuelve ningún valor.

SOBRESCRIBIR MIEMBROS HEREDADOS


Tal como comentamos en el artículo anterior, todas las clases de .NET se derivan directa o
indirectamente de la clase Object. La clase definida en el listado 1 también se deriva de Object aunque
no se indique expresamente.
La clase base de todas las clases de .NET tiene un método que se utiliza para recuperar información,
(en formato cadena), del contenido de la clase: el método ToString.
Cada clase puede crear su propia versión del método ToString para que devuelva una representación
adecuada, por ejemplo los tipos numéricos devuelven una cadena que representa al número que
contiene.
En nuestra clase podemos redefinirlo para que nos devuelva el contenido de los dos datos que la clase
mantiene:
Public Overrides Function ToString() As String
Return String.Format("{0}, {1}", _prop1, _prop2)
End Function

Para indicarle al compilador que estamos redefiniendo un método ya existente, lo indicamos con la
instrucción Overrides (override en C#). Debido a que ahora nuestra clase tiene una nueva versión de
este método, cualquier clase que se derive de ella también heredará la nueva implementación del
método ToString.

USAR LA HERENCIA EN VISUAL BASIC .NET


La clase que hemos definido en el listado 1 no indicaba de ninguna forma que se deriva de la clase
Object, este es un caso excepcional, ya que todas las clases de .NET se derivan de forma "automática"
de la clase Object.
Para indicar en Visual Basic .NET que una clase se deriva de otra, debemos usar la instrucción (o
palabra clave) Inherits seguida de la clase que queremos usar como base.
Esa instrucción tiene que indicarse al principio de la declaración de la clase, antes que cualquier otra
instrucción, con excepción de los comentarios.
Para crear una clase que se derive de la clase A definida en el listado 1, tendríamos que hacer lo
siguiente:
Public Class B
Inherits A
End Class

Tal como podemos comprobar, la clase B no define ningún método ni propiedad, pero realmente si que
tiene métodos y propiedades: todos los que tenga la clase A (además de los de la clase Object).
Para comprobarlo podemos definir una variable del tipo de la clase B y comprobaremos que esta clase
tiene los mismos miembros que la clase A, tal como se muestra en el listado 2.
Sub Main()
Dim objB As New B
objB.Prop1 = "guille"
objB.Prop2 = 47

objB.Mostrar()
Console.WriteLine("{0}", objB.ToString)
End Sub

OCULTAR MIEMBROS HEREDADOS


Todo funciona como esperábamos, aunque hay un pequeño problema, si quisiéramos modificar el
comportamiento de los miembros heredados por la clase B, no podríamos hacerlo. La razón es bien
simple: la clase A no ha definido los miembros como virtuales (o reemplazables). Por tanto no se
pueden crear nuevas versiones de los mismos en la clase B, o casi...
Realmente la clase B si que puede definir nuevas versiones de los miembros que tiene la clase A, al
menos puede crear métodos y propiedades que tengan el mismo nombre, por ejemplo:
Public Sub Mostrar()
Console.WriteLine("Mostrar en la clase B: {0}, {1}", Prop1, Prop2)
End Sub

Esto es totalmente correcto, al menos en el sentido de que no produce ningún error; lo más que
producirá esa declaración es una advertencia del compilador indicándonos que ese método entra en
conflicto con el definido en la clase base A, tal como podemos comprobar en la figura 1.

Esa advertencia nos informa que deberíamos indicar que la declaración "oculta" a la definida en la
clase A y por tanto deberíamos usar la instrucción Shadows (new en C#). Aunque usemos Shadows, el
problema real sigue existiendo: el método declarado en la clase B oculta al declarado (y heredado) en la
clase A.
Si después de definir este método de la clase B volvemos a ejecutar el código del listado 2,
comprobaremos que se utiliza el nuevo método.
Posiblemente el lector pensará que eso es lo que queríamos conseguir: tener nuestra propia versión del
método Mostrar. Es más, si definimos una nueva clase que se derive de B podemos comprobar que
realmente es ese método el que se hereda por la nueva clase.
En el listado 3 podemos ver la definición de la clase C y el código para comprobar lo que mostraría:
Public Class C
Inherits B
End Class

Sub Main()
Dim objC As New C
objC.Prop1 = "guille"
objC.Prop2 = 47

objC.Mostrar()
Console.WriteLine("{0}", objC.ToString)
End Sub

Y lo que imprime es exactamente lo mismo que usando la clase B. Por tanto, el objetivo está
conseguido, es decir, la clase B ha "redefinido" un método de la clase A y esa nueva versión es la que
se usará a partir de ese momento por las clases que se basen en la clase B.
Aparentemente así es. Al menos si lo tomamos al pie de la letra.
El único problema es que acabamos de romper una de las cualidades de la programación orientada a
objetos: el polimorfismo.
Esta nueva definición del método Mostrar ya no tiene nada que ver con la definida por la clase A y por
tanto no existe ninguna relación "polimórfica" entre ambos métodos.
Para ser más precisos, tanto la clase B como la clase C tienen dos definiciones del método Mostrar: el
inicialmente heredado de la clase A y el nuevo definido por la clase B, aunque siempre prevalecerá el
definido expresamente en la clase derivada frente al heredado de la clase base.
Si pudiésemos ver el objeto creado en la memoria a partir de la clase B (e incluso de la clase C), nos
daríamos cuenta de que realmente está dividido en tres partes, tal como se muestra en la figura 2:
1. La parte heredada de la clase Object
2. La parte heredada de la clase A
3. Las definiciones propias de la clase B
El método ToString definido en Object ha sido reemplazado o, para que lo comprendamos mejor,
sustituido por el redefinido en la clase A, pero el método Mostrar de la clase A aún existe, lo que
ocurre es que ha sido ocultado por el que se ha definido en la clase B.
Para demostrar que es así, que existen dos métodos Mostrar en la memoria, podemos utilizar el
polimorfismo para "extraer" el método Mostrar que está oculto.
Para ello tendremos que declarar una variable del tipo de la clase A y decirle que extraiga del objeto B
la parte que le corresponde: solo la parte definida en la clase A.
Esto se consigue de una forma muy simple: asignando a una variable del tipo A el contenido de la
variable que apunta al objeto B:
Dim objA As A
objA = objB

A partir de este momento el objeto B está siendo referenciado por el objeto A, pero, y esto es
importante, solo la parte que A conoce, es decir, la variable objA solamente podrá acceder al "trozo"
del objeto B que se derivó de la clase A.
Por tanto, si llamamos al método Mostrar de la variable objA, accederemos al método Mostrar que el
objeto B contiene porque la consiguió al derivarse de A.
Sí, esto es algo complicado y que no es fácil de comprender, pero es importante intentar asimilarlo, ya
que es muy probable que lo necesitemos en nuestros proyectos, sobre todo si sabemos que se puede
hacer.
Además de que esta sería la única forma que tenemos de acceder a ese método "oculto", ya que
cualquier intento de acceder al método Mostrar mediante un objeto del tipo B, siempre accederá al
definido en la propia clase B y que oculta al heredado de la clase A.
NOTA:
Cuando declaramos una variable y la instanciamos, (creando un nuevo objeto a partir de una clase),
dicha variable simplemente tiene una referencia al objeto creado en la memoria, (la variable
simplemente tiene un puntero al objeto real), por tanto, ese objeto existe y puede ser referenciado por
otras variables, aunque esas otras variables deben ser de tipos "incluidos" en la clase usada para crear
dicho objeto.
Al instanciar un nuevo objeto del tipo B, (tal como se muestra en la figura 2), podemos acceder a él
mediante variables de tipo Object, de tipo A y, por supuesto, de tipo B.

INDICAR LOS MIEMBROS VIRTUALES


Como hemos comprobado, si queremos que los miembros de nuestra clase se puedan redefinir en clases
derivadas, debemos indicarlo de forma expresa.
Para que esto sea así, utilizaremos la instrucción Overridable (virtual en C#). De esta forma le
indicaremos al compilador que el método se puede redefinir, es decir, que en la clase derivada se puede
crear una nueva versión "personalizada" de dicho miembro.
Con esto logramos que en sucesivas derivaciones de la clase solamente exista un mismo miembro
polimórfico.
La ventaja principal es que si en otra clase decidimos crear una nueva versión de, por ejemplo, un
método, cuando se cree un objeto en la memoria, solo existirá ese método, no varios métodos con el
mismo nombre, pero sin ninguna relación entre ellos, exceptuando el hecho de que se llamen de la
misma forma.
Tal como vimos en la sección SOBRESCRIBIR MIEMBROS HEREDADOS, tendremos que usar la
instrucción Overrides (override en C#), para indicar que nuestra intención es crear una versión propia
de uno de los miembros heredados.
Pero no solo basta con usar esa instrucción, ya que si queremos redefinir un miembro existente en
alguna de las clases que hemos heredado, la "firma" de nuestra versión debe ser la misma que el
original. Es decir, si es un método que devuelve una cadena y no recibe parámetros, debemos "respetar"
esas mismas características, porque de lo que se trata es de cumplir con un contrato, dicho contrato
estipula que tipo de miembro es, (un método o una propiedad), que tipo de datos devuelve, si recibe o
no parámetros y en caso de que los reciba de que tipo deben ser.
Todo esto es para garantizar que esos miembros se puedan usar de forma independiente de la clase en el
que se ha declarado, con idea de que podamos acceder a ellos mediante objetos de las clases que se han
usado para crear la nueva, (las clases de las que se deriva la clase que sobrescribe el método).
Por ejemplo, si el método Mostrar definido en la clase A se hubiese declarado como virtual,
(Overridable), y en la clase B lo hubiésemos redefinido usando la instrucción Overrides, podríamos
acceder a dicho método (de un objeto creado a partir de la clase B) usando tanto una variable de la
clase A como una de la clase B; ya que en la memoria solamente existirá un método llamado Mostrar.
A diferencia de lo que ocurría antes (al no declararlo como virtual), que realmente existían dos métodos
Mostrar.

DEFINIENDO INTERFACES
Una interfaz realmente es la definición de los miembros públicos de una clase. Pero en los lenguajes de
programación de .NET también podemos definir clases especiales que simplemente definan cómo
deben ser los miembros que una clase implemente. Es decir que características deben tener. De esta
forma podemos garantizar que si varias clases implementan los miembros definidos en una interfaz,
podemos usarlos de manera anónima, es decir, sin necesidad de saber si estamos usando un objeto de
una clase o de otra, ya que si ambas clases implementan la interfaz, tendremos la certeza de que dichas
clases tienen los miembros definidos en dicha interfaz.
Una interfaz representa un contrato, si una clase implementa una interfaz, está suscribiendo dicho
contrato, es más, está obligada a cumplirlo, por tanto, la clase tendrá que definir todos los miembros
que la interfaz contenga.
Antes de ver cómo usar las interfaces en nuestras clases, veamos cómo definir una interfaz.
Public Interface IPrueba2
Property Prop1() As String
Sub Mostrar()
End Interface

En este caso hemos definido una interfaz llamada IPrueba2 (por convención los nombres de las
interfaces siempre empiezan con la letra I mayúscula), en la que se define una propiedad y un método.
Los miembros de las interfaces siempre son públicos y no deben implementar código, solamente la
definición propiamente dicha.

UTILIZAR INTERFACES EN LAS CLASES


Cualquier clase que quiera disponer de los miembros definidos en la interfaz debe indicarlo de forma
expresa, ya que no solo es suficiente con definir los miembros con nombres y características similares.
Para "implementar" en una clase los miembros definidos en una interfaz tendremos que usar la
instrucción Implements seguida del nombre de la interfaz. Además tendremos que definir los métodos
y propiedades que dicha interfaz contiene, aunque en Visual Basic además hay que indicarlo
expresamente, de forma que se sepa con seguridad de que cada uno de esos miembros equivale a los
definidos en la interfaz.
En el listado 4 vemos cómo definir una clase que utilice la interfaz que acabamos de ver en la sección
anterior.
Public Class Prueba2
Implements IPrueba2

Public Sub Mostrar() _


Implements IPrueba2.Mostrar
' nuestra version del método Mostrar
End Sub

Public Property Prop1() As String _


Implements IPrueba2.Prop1
Get
' el código que devuelve
' el valor de la propiedad
End Get
Set(ByVal value As String)
' el código que asigna
' el valor de la propiedad
End Set
End Property
End Class

El método y las dos propiedades deben tener el mismo nombre y parámetros (si los hubiera) que los
definidos en la interfaz.
Cuando trabajamos con Visual Basic además debemos indicar expresamente que dicho método o
propiedad está "ligado" con el definido en la interfaz, cuando trabajamos con C# no es necesario
indicarlo.
La ventaja de esta "redundancia" de VB es que podemos dar un nombre diferente al miembro
implementado, pero "internamente" el compilador sabrá que nos estamos refiriendo al que implementa
la interfaz.
Tal como podemos ver en el listado 5, a pesar de que en la clase Prueba2B al método le hemos dado
otro nombre, realmente está haciendo referencia al que se ha declarado en la interfaz.
Public Class Prueba2B
Implements IPrueba2

Public Sub OtroNombre() _


Implements IPrueba2.Mostrar
Console.WriteLine("Este es el método Mostrar de la clase Prueba2B")
End Sub

Public Property Prop1() As String _


Implements IPrueba2.Prop1
Get
Return "Prop1 de la clase prueba2B"
End Get
Set(ByVal value As String)
'
End Set
End Property
End Class
Dim p2 As New Prueba2
Dim p2b As New Prueba2B
Dim i As IPrueba2
'
i = p2
i.Mostrar()
'
i = p2b
i.Mostrar()

POLIMORFISMO USANDO CLASES E INTERFACES


En los ejemplos mostrados ya hemos visto cómo usar el polimorfismo tanto a través de variables
incluidas en las clases como con interfaces que dichas clases implementan; vamos a detallar un poco
más, ya que esta es una de las características más usadas en .NET, por la sencilla razón de que muchas
clases de .NET Framework implementan interfaces de forma que podamos acceder a los miembros
implementados por medio de variables del tipo de dichas interfaces.
Es más, muchas de las clases de .NET además permiten que demos nueva funcionalidad a nuestras
propias clases si implementamos ciertas interfaces.
Por ejemplo, si queremos que nuestra clase sea "clasificable", es decir, que se pueda usar en una
colección que clasifique los elementos que contiene, nuestra clase debe implementar la interfaz
IComparable.
Si definimos una clase en la que queremos que el método ToString actúe de forma que podamos
especificar ciertos formatos a la hora de mostrar el contenido, nuestra clase debe implementar
IFormattable.
Que queremos que nuestra clase actúe como una colección, en la que se pueda enumerar o recorrer el
contenido de la misma, debemos implementar la interfaz IEnumerable.
Pero esas interfaces propias del .NET Framework lo que harán será darle una nueva funcionalidad a
nuestras clases, por tanto, lo importante es saber de que forma actúan los objetos creados en la
memoria.
Tal como hemos comentado antes, solo existe un objeto en la memoria y cuando accedemos a él lo
podemos hacer bien usando alguna de las clases de las que se deriva o bien mediante alguna de las
interfaces que implementa.
Cuando accedemos a dicho objeto mediante algunas de estas clases o interfaces simplemente estamos
accediendo a la parte "conocida" por dicho tipo.
En el código del listado 4 y 5 la interfaz IPrueba2 "sabe" cómo acceder al método Mostrar y a la
propiedad Prop1, independientemente del objeto que la haya implementado, por tanto si una clase
implementa dicha interfaz podemos acceder a esos miembros mediante una variable del tipo IPrueba2,
tal como se demuestra en el listado 5 en el que accedemos al método Mostrar definido en la interfaz e
implementado por las dos clases.

USAR EL POLIMORFISMO PARA ACCEDER A ELEMENTOS DIFERENTES DE UN


ARRAY O COLECCIÓN
Una de las utilidades del polimorfismo (de clases o interfaces) es que podemos crear arrays de variables
de un tipo "básico" y en ese array incluir objetos que si bien son distintos, en el fondo tienen como
parte componente la clase de la que se ha declarado el array.
El ejemplo más básico y válido tanto para las clases declaradas en el propio .NET Framework como las
declaradas por nosotros mismos, es crear un array de tipo Object, dicho array podrá contener objetos
de cualquier tipo (incluso tipos como números enteros, cadenas, etc.), y posteriormente poder acceder a
cualquiera de los elementos mediante un objeto del tipo Object, en cuyo caso solo podremos acceder a
los métodos que Object implementa o bien mediante objetos de un tipo en particular, en cuyo caso nos
veremos obligados a hacer una conversión desde el tipo contenido en el array (Object) al tipo
particular que nos interese.
En el listado 6 podemos ver un ejemplo en el que se crea un array de tipo Object pero que se
almacenan tanto objetos del tipo clase A, clase B, IPrueba2, Integer y String.
Dim a(6) As Object
'
Dim a1 As New A
a1.Prop1 = "Objeto A"
Dim b1 As New B
b1.Prop1 = "Objeto B"
Dim c1 As New C
c1.Prop1 = "Objeto C"
'
a(0) = a1
a(1) = b1
a(2) = c1
a(3) = New Prueba2
a(4) = New Prueba2B
a(5) = 15
a(6) = "Hola"
'
Dim i As Integer
Console.Write("Usando el método ToString")
Console.WriteLine(" de los objetos contenidos")
For i = 0 To a.Length - 1
Console.WriteLine("a({0}) = {1}", i, a(i).ToString())
Next
'
Console.WriteLine()
'
Console.WriteLine("Usando un Object")
Dim o As Object
For Each o In a
Console.WriteLine("o.ToString = {0}", o.ToString())
Next
'
Console.WriteLine()
'
Console.WriteLine("Usando tipos específicos")
For Each o In a
Console.WriteLine("El tipo es: {0}", o.GetType().Name)
If TypeOf o Is A Then
Dim tA As A = CType(o, A)
tA.Mostrar()
ElseIf TypeOf o Is IPrueba2 Then
Dim tIPrueba2 As IPrueba2 = CType(o, IPrueba2)
tIPrueba2.Mostrar()
ElseIf TypeOf o Is Integer Then
Dim tInt As Integer = CType(o, Integer)
Console.WriteLine(tInt.ToString("00000"))
ElseIf TypeOf o Is String Then
Dim tStr As String = o.ToString
Console.WriteLine(tStr)
Else
Console.WriteLine("o.ToString = {0}", o.ToString())
End If
Next

Tal como podemos comprobar en el último bucle de dicho listado, se utiliza la instrucción compuesta
TypeOf ... Is para saber si un objeto es de un tipo concreto (en C# usaríamos is), también podemos ver
que usando el método GetType podemos obtener el tipo subyacente así como el nombre de dicho tipo.
Si nos fijamos, al hacer la comprobación TypeOf o Is A aquí se procesarán tanto los objetos del tipo A
como los derivados de dicho tipo, lo mismo ocurre con la interfaz IPrueba2.
Pero este ejemplo al ser genérico y usando la clase Object seguramente no acabará de "cuajar", por
tanto vamos a crear un ejemplo en el que crearemos variables de un tipo concreto: Cliente y
derivaremos un par de clases en las que agregaremos nueva funcionalidad a la clase base,
posteriormente crearemos un array del tipo Cliente en el que podremos almacenar variables de
cualquiera de esos tipos derivados de ella.
Nota:
Por la extensión del listado, el mismo se incluye en el ZIP con el código de los ejemplos (tanto para
Visual Basic como para C#), en el listado 7 puedes ver cómo usar esas clases.
En dicho código tendremos ocasión de ver cómo podemos implementar la interfaz IComparable para
que estas clases se puedan agregar a una colección y posteriormente clasificarlas.
Además implementaremos la interfaz IFormattable para que, si los mostramos por la consola o
usamos el método Format de la clase String, podamos usar los siguientes formatos personalizados:
• ANS mostrará los apellidos, el nombre y el saldo
• NAS mostrará el nombre, los apellidos y el saldo
• AN mostrará los apellidos y el nombre (predeterminado)
• NA mostrará el nombre y los apellidos
• S mostrará el saldo
Sub Main()
Dim acli(6) As Cliente
'
acli(0) = New Cliente("Jose", "Sanchez", 125.5D)
acli(1) = New ClienteOro("Luis", "Rebelde", 2500.75D)
acli(2) = New ClienteMoroso("Antonio", "Perez", -500.25D)
acli(3) = New Cliente("Miguel", "Rodriguez", 200)
acli(4) = New ClienteMoroso("Juan", "Ruiz", -310)
acli(5) = New ClienteOro("Mariano", "Alvarez", 500.33D)
acli(6) = New Cliente("Carlos", "Bueno", 975)
'
Console.WriteLine("Antes de clasificar:")
For Each c As Cliente In acli
Console.WriteLine("{0}, saldo= {1}", c, c.MostrarSaldo())
Next
Array.Sort(acli)
'
Console.WriteLine()
Console.WriteLine("Después de clasificar:")
For Each c As Cliente In acli
Console.Write("{0}, saldo= {1}", c, c.MostrarSaldo())
If TypeOf c Is ClienteOro Then
Console.WriteLine(" -> $$$ es cliente ORO $$$")
ElseIf TypeOf c Is ClienteMoroso Then
Console.WriteLine(" -> OJO que es un cliente moroso")
Else
Console.WriteLine()
End If
Next
'
Console.WriteLine()
Console.WriteLine("Mostrar usando formatos:")
For Each c As Cliente In acli
Console.WriteLine("Usando NAS= {0:NAS}", c)
Console.WriteLine("Usando AN= {0:AN}", c)
Console.WriteLine("Usando S= {0:S}", c)
Next
'
Console.ReadLine()
End Sub

CONSTRUCTORES Y SOBRECARGA DE CONSTRUCTORES


El punto de inicio de cualquier clase, cuando se crea una instancia en la memoria, es un método
especial al que se le conoce como constructor.
Un constructor no devuelve ningún valor, por tanto en Visual Basic sería un método de tipo Sub
llamado New (en C# no se declara como void, simplemente tendrá el mismo nombre de la clase).
Los constructores también se pueden sobrecargar, es decir, pueden existir varias versiones en las que
cada una de ellas reciba distintos parámetros, en número y/o en tipo.
Debido a que todas las clases (y estructuras) deben tener un constructor, si nosotros no lo definimos de
forma expresa, será el propio compilador el que se encargue de añadirlo por nosotros, en ese caso será
un constructor en el que no se reciba ningún argumento.
Aunque hay que tener presente que en el momento en que hemos definido un constructor el compilador
ya no agregará ninguno de forma automática. Esto tiene sus ventajas, ya que en ocasiones es posible
que nos interese que nuestras clases solamente se puedan instanciar si se le pasa algunos parámetros al
constructor de la misma.
Pero si además de un constructor con parámetros queremos seguir manteniendo el constructor
"predeterminado", entonces tendremos que declararlo aunque no escribamos nada de código en el
interior.
En el listado 8 podemos ver la clase Cliente con tres constructores.
Public Class Cliente
Implements IComparable, IFormattable
'
Private _nombre As String
Private _apellidos As String
Private _saldo As Decimal
'
Public Sub New()
End Sub

Public Sub New( _


ByVal elNombre As String, _
ByVal losApellidos As String)
_nombre = elNombre
_apellidos = losApellidos
End Sub

Public Sub New( _


ByVal elNombre As String, _
ByVal losApellidos As String, _
ByVal elSaldo As Decimal)
_nombre = elNombre
_apellidos = losApellidos
_saldo = elSaldo
End Sub

LOS CONSTRUCTORES NO SE HEREDAN


El constructor es el único miembro de una clase que no se hereda, por tanto si necesitamos crear
constructores en clases derivadas debemos declararlos de forma explícita.
LOS CONSTRUCTORES DE LAS ESTRUCTURAS
Los constructores de las estructuras son un caso especial, ya que siempre existirá un constructor sin
parámetros, el cual además no podemos definirlo por medio de código, porque es el propio compilador
el que se encargará de su creación. Por tanto en las estructuras solamente podemos definir constructores
parametrizados (que reciban parámetros) y en el caso de definirlo, en el código del mismo, tendremos
que asignarle un valor a cualquiera de las propiedades (o campos) públicos que tenga esa estructura,
siempre y cuando no sean estáticos (compartidos).

CONSTRUCTORES QUE USAN OTROS CONSTRUCTORES


Debido a que los constructores pueden recibir parámetros, en algunas ocasiones nos puede ser útil
poder llamar a otros constructores, por ejemplo de la clase base o de la misma clase.
En estos casos, en Visual Basic es fácil hacerlo, como sabemos que los constructores realmente son
métodos llamados New, los podemos usar de la misma forma que haríamos con cualquier otro método.
Por ejemplo, si modificamos el código mostrado en el listado 8, podríamos hacer esto:
Public Sub New( _
ByVal elNombre As String, _
ByVal losApellidos As String, _
ByVal elSaldo As Decimal)

Me.New(elNombre, losApellidos)
_saldo = elSaldo

End Sub

De forma que desde el constructor que recibe tres parámetros llamemos al que recibe dos.
En este caso, la instrucción o palabra clave Me representa a la instancia actual (el objeto que se ha
creado en memoria).
Si en lugar de llamar a otro constructor de la propia clase, queremos llamar a un constructor de la clase
base, en lugar de Me, usaremos MyBase.
En C# este mismo código se haría de la siguiente forma:
public Cliente(string elNombre,
string losApellidos,
decimal elSaldo)
: this(elNombre, losApellidos)
{
_saldo = elSaldo;
}

Es decir, se llamaría al otro constructor indicándolo después del cierre de paréntesis y separándolo con
dos puntos.
En C# la instrucción o palabra clave que hace referencia a la instancia actual es this y la que hace
referencia a la clase base es: base.

CLASES ABSTRACTAS
Tal como comentamos el mes anterior, en ocasiones tendremos la necesidad de crear clases que no se
puedan usar para crear objetos, pero si para usarla como base de otras.
La razón principal para que hacer que una clase no sea instanciable es que dicha clase por sí sola no
tenga ningún sentido, al menos para poder crear nuevos objetos de ese tipo, pero si tendrá sentido si la
usamos como clase base de otras clases.
Por ejemplo, podríamos tener una clase Animal, la cual no tendría sentido si a partir de ella se pudiesen
crear nuevos objetos, ya que el concepto de animal es demasiado abstracto para poder crear un objeto a
partir de él. Pero si la podríamos utilizar para derivar de ella otras clases que bien podrían ser a la vez
abstractas o bien clases "normales".
.NET Framework nos permite crear clases abstractas, las cuales son como las interfaces, pero las que
pueden tener métodos y otros miembros que tengan no solo la definición de esos miembros sino
también código funcional. Además, debido a que las clases abstractas están pensadas para usarse como
clases base de otras, todos los miembros son virtuales de forma predeterminada, por tanto no es
necesario indicarlos usando el modificador Overridable (virtual en C#).

DECLARAR CLASES ABSTRACTAS


La definición de una clase abstracta se hace como las clases normales, salvo de que hay que usar el
modificador MustInherit (abstract en C#), de esta forma le indicamos al compilador de que nuestra
intención es la de que nuestra clase sea "heredable".
Como ya hemos comentado anteriormente, el concepto de clases abstractas o clases que solo se pueden
usar como clases base es importante cuando queremos ofrecer cierta funcionalidad a nuestras clases,
sobre todo las que formen parte de ciertas jerarquías de clases, ya que las clases abstractas nos servirán
para definir el comportamiento del resto de las clases, además de que, como las interfaces, nos
permitirán disponer de cierta funcionalidad o, mejor dicho, nos permitirán dar unas pautas que los que
decidan usarlas, tendrán que seguir.
Pero a diferencia de las interfaces, las clases abstractas no solo representarán un contrato, sino que
también pueden ofrecer "de fábrica" un funcionamiento que, aunque las clases derivadas no
implementen, éstas ya tendrán, en este aspecto funcionarán como las clases normales, ya que las clases
derivadas podrán usar el código implementado en ellas.
MIEMBROS ABSTRACTOS
Cuando comentamos que las clases abstractas son como las interfaces no solo nos referíamos a que se
podrían usar para proporcionar polimorfismo, sino porque en las clases abstractas también podemos
definir miembros que a su vez sean abstractos, es decir, que en las clases abstractas solamente se defina
el método o propiedad, pero que no tenga ninguna funcionalidad, es más, si declaramos un miembro
como abstracto la clase que se derive de la clase abstracta estará obligada a definirlo.
Esto nos ofrece la ventaja de poder definir miembros funcionales y miembros que solo tengan sentido
en las clases derivadas y por tanto serán las clases derivadas las que deban definir el código que los
haga funcionales y le den la utilidad adecuada.
Para indicar que un miembro es abstracto, debemos indicarlo usando la instrucción MustOverride
(abstract en C#), de esta forma nos permitirá el compilador escribir solo la definición del mismo y no
tener que escribir ningún código.
En las clases derivadas tendremos que usar la instrucción Overrides (override en C#) de la misma
forma que lo usamos con el resto de las clases para indicar que dicho método está reemplazando a un
miembro definido en la clase base.
Una puntualización: Los miembros abstractos solo se pueden definir en clases abstractas.
En el código incluido en el ZIP que acompaña a este artículo se incluye un ejemplo que define y usa las
clases abstractas, como es habitual, se incluye código para Visual Basic y C#.

CLASES SELLADAS (NO HEREDABLES)


Otro concepto, que posiblemente pueda parecer que no está "ligado" con la herencia es el de las clases
selladas o no heredables.
Este tipo de clases no permitirán que se usen como clases base de otras nuevas clases.
La existencia de las clases normales y las abstractas nos permiten derivar nuevas clases a partir de ellas,
eso tiene sentido, pero, ¿qué sentido puede tener una clase de la que no se puedan derivar nuevas
clases?
La razón principal para definir una clase como sellada o no heredable es precisamente porque no
queremos que nadie pueda modificar el comportamiento de dicha clase, ya que al no poder usarla como
base de nuevas clases, nadie podrá ofrecer nueva funcionalidad, de esta forma nos aseguramos que esa
clase siempre funcionará como la hemos definido.
Esto es así, porque al estar "sellada" tampoco podremos definir miembros virtuales, por la sencilla
razón de que nadie podrá derivar nuevas clases y por tanto tampoco podrá reemplazar el
comportamiento de los mismos.
Debido a que el comportamiento normal de una clase es que sea heredable, para poder crear clases que
estén selladas, y por tanto hacerlas no heredables, debemos usar la instrucción o modificador
NotInheritable (sealed en C#).
Cuándo utilizar la herencia
La herencia es un concepto de programación útil, pero es fácil utilizarlo de manera poco adecuada. A
menudo, las interfaces solucionan mejor la situación. Este tema y Cuándo se deben utilizar interfaces le
ayudan a entender cuándo se debe utilizar cada enfoque.
La herencia es una buena opción cuando:
• La jerarquía de herencia representa una relación de identidad y no una relación de pertenencia.
• Se puede volver a utilizar código de las clases base.
• Es necesario aplicar la misma clase y los mismos métodos a tipos de datos diferentes.
• La jerarquía de clases es poco profunda y es poco probable que otros programadores agreguen
muchos más niveles.
• Desea realizar cambios globales en clases derivadas modificando una clase base.
Estas consideraciones se explican en este orden a continuación.
Herencia y relaciones de identidad

Dos maneras de mostrar las relaciones de clase en la programación orientada a objetos son las
relaciones de identidad y de pertenencia. En una relación de identidad, la clase derivada es claramente
un tipo de clase base. Por ejemplo, una clase denominada PremierCustomer representa una relación de
tipo identidad con una clase base denominada Customer, puesto que un cliente principal es un cliente.
No obstante, una clase denominada CustomerReferral representa una relación de pertenencia con la
clase Customer porque una cartera de clientes tiene un cliente, pero no es un tipo de cliente.
Los objetos de una jerarquía de herencia deben tener una relación de identidad con la clase base puesto
que heredan los campos, propiedades, métodos y eventos definidos en dicha clase. Las clases que
representan una relación de pertenencia con otras clases no son válidas para jerarquías de herencia
debido a que podrían heredar propiedades y métodos inadecuados. Por ejemplo, si la clase
CustomerReferral se derivase de la clase Customer descrita anteriormente, podría heredar propiedades
que no tendrían sentido, como ShippingPrefs y LastOrderPlaced. Las relaciones de pertenencia como
ésta deben representarse mediante clases o interfaces no relacionadas. La siguiente ilustración muestra
ejemplos de relaciones de tipo "es un" y "tiene un".
Clases base y reutilización de código

Otra razón para usar la herencia es la ventaja de poder reutilizar el código. Las clases bien diseñadas,
una vez depuradas, pueden utilizarse una y otra vez como base de nuevas clases.
Un ejemplo común de reutilización eficaz de código está relacionado con bibliotecas que administran
estructuras de datos. Por ejemplo, suponga que tiene una gran aplicación comercial que administra
varias clases de listas en la memoria. Una es una copia en memoria de la base de datos de clientes, que
se lee desde una base de datos al iniciar la sesión para conseguir mayor velocidad. La estructura de
datos tendría un aspecto similar al siguiente:

Das könnte Ihnen auch gefallen