martes, 6 de mayo de 2014

Interfaces en C# - Parte 6

Tabla de Contenido

0. Introducción
1. Interfaces y Boxing de structs
2. Decisión de Diseño: Clases vs. Interfaces
3. Conclusiones
4. Glosario
5. Enlaces & Literatura

0. Introducción

Esta última parte de la serie de artículos de Interfaces en C# comprende dos temas: Boxing y unboxing en interfaces con structs, y decisión de diseño: clases vs interfaces. Del primer tema ya poseemos los esenciales -cfr. Boxing y Boxing en C#- ahora veremos cómo funciona este mecanismo de conversión entre interfaces y structs; luego pasaremos a ver las reglas de decisión de diseño para determinar si usamos un tipo como una clase o una interfaz.

1. Interfaces y Boxing de structs

Recordemos que los procesos de boxing y unboxing son útiles para convertir tipos por valor hacia y desde tipos por referencia (objetos), respectivamente. Así mismo ya conocemos acerca de la naturaleza de los structs, tipos ideales para la representación de estructuras con semántica de tipos por valor -crf. Structs en C#-. Ahora vamos a ver cómo podemos relacionar los conceptos de structs, interfaces, y los procesos de boxing y unboxing.

En primera instancia vamos demostrar a través de ejemplos el proceso de boxing sobre structs:

Creamos una interfaz:

public interface Interfaz
{
void Metodo();
}

Ahora definimos un struct:

public struct S : Interfaz
{
public void Metodo()
{
Console.WriteLine("Método implementado en el struct S.");
}
}

En código cliente podemos realizar las siguientes operaciones de boxing y unboxing:

public static void Main()
{
S s1 = new S();
I s2 = new S();
Interfaz i1 = s2;

S s3 = (S)i1; // Unboxing

Interfaz i2 = s3; // Boxing

i1.Metodo();
s2.Metodo();
}

> Prueba de ejecución.

2. Decisión de Diseño: Clases vs. Interfaces

Sabemos que el único factor de parecido entre una clase y una interfaz reside en que ambas construcciones pueden contener métodos abstractos: cuando una clase posee uno o varios métodos abstractos se le conoce como clase abstracta (y no se puede crear una instancia de este tipo); mientras que una interfaz sólo puede contener métodos abstractos. Una pregunta natural que puede emerger frente a esto podría ser:

¿Cuándo usar una interfaz en lugar de una clase? En [1] nos dan una respuesta interesante y concreta a esta cuestión a modo de principios o directivas de diseño:
  • Diseñe clases y subclases para aquellos tipos que compartan de forma natural una implementación a través de sus miembros.
  • Diseñe interfaces para aquellos tipos que mantengan implementaciones independientes.
Veamos esto a través de este ejemplo:

Definamos el siguiente conjunto de clases:

abstract class Animal
{
}

abstract class Parajo : Animal
{
}

abstract class Insecto : Animal
{
}

abstract class Volador : Animal
{
}

abstract class Carnivoro : Animal
{
}

Gráficamente en UML:
Jerarquía de tipos de reino animal
Figura 1. Jerarquía de tipos de reino animal.

Tipos de datos (clases) concretos:

public class Gallina : Pajaro
{
}

public class Agila : Parajo, Volador, Carnivoro
{
}

Miremos qué ocurre si compilamos este ejemplo de código:


La primera herencia -class Gallina : Pajaro- es correcta, mientras que la segunda -class Agila : Parajo, Volador, Carnivoro- no. Cuando intentemos compilar obtendremos el error CS1721 [4]:

error CS1721: `Agila': Classes cannot have multiple base classes (`Parajo' and `Volador')
error CS1721: `Agila': Classes cannot have multiple base classes (`Parajo' and `Carnivoro')

Esto nos transmite el mensaje de que no está permitido la herencia múltiple.

Este error también ocurrirá en intentos como:

public class Abeja : Insecto, Volador
{
}

public class Pulga : Insector, Carnivoro
{
}

¿Qué podemos hacer? La respuesta rápida reside en segundo principio: diseñar interfaces para que los tipos que contengan implementaciones independientes. Entonces, ¿cuáles tipos de los anteriores deberíamos especificar como interfaces? Los tipos Parajo, e Insecto comparten implementación entre los subtipos que le correspondan en la jerarquía.

Continuando, tengamos en mente que cada especie de animal en la naturaleza posee mecanismos diferentes para volar, al igual no todos los carnívoros ingieren los alimentos usando las mismas estrategias de consumo.

La razón previa nos convence de que los tipos Volador y Carnivoro se convertirán en interfaces:

public interface IVolador
{
}

public interface ICarnivoro
{
}

Veamos ahora el diagrama UML de jerarquía del reino animal actualizado:
Jerarquía de tipos de reino animal
Figura 2. Jerarquía de tipos de reino animal.

Conclusiones

Los procesos de boxing y unboxing están disponibles para los structs cuando éstos implementan interfaces. Por otro lado, en la segunda sección aprendimos que las clases deben ser elegidas sobre las interfaces, cuando se comparte implementación entre los miembros de una jerarquía de herencia; en contraste, las interfaces son adecuadas para las situaciones donde los tipos tienen implementaciones independientes.

Glosario

  • Boxing
  • Clase
  • Clase abstracta
  • Interfaz
  • struct
  • Unboxing

Enlaces & Literatura

[1]: C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[2]: Boxing y Unboxing en C# | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/05/interfaces-en-c-parte-5.html
[3]: Structs en C# | OrtizOL - Experiencias Construcción Software (xCSw) - 
http://ortizol.blogspot.com/2014/04/structs-en-c.html
[4]: Compiler Error CS1721 - http://msdn.microsoft.com/en-us/library/vstudio/c028c0t7%28v=vs.100%29.aspx


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.