El uso de interfaces

by Valeriano Tortola 28. agosto 2007 12:12

Últimamente me estoy viendo más en la necesidad de usar interfaces y genéricos para crear clases más reusables y tipadas. En mis inicios con C# y .NET... aunque la idea de los genéricos me gustó... a las interfaces ... no les veia demasiada utilidad. A día de hoy soy consciente de la tremenda simbiosis que tienen estos dos elementos para crear código reusable.

Una interfaz, es como la carcasa que envuelve a un dispositivo, la carcasa no tiene funcionalidad en si, pero representa la funcionalidad que que tiene el dispositivo que en ella encaja (pensad en un acople funcional y no físico :P).

Por ejemplo, la carcasa de un móvil tiene (entre otras cosas) un agujero para un micrófono, otro para el altavoz, un teclado y una pantalla, define entonces la funcionalidad de un dispositivo con el que se puede hablar, escuchar, escribir y ver. Al acoplar a la carcasa el dispositivo móvil, podrémos llamar y enviar SMSs a través de estos elementos en dicho dispositivo móvil, pero, si tuviesemos una PDA que tuviese definidas las funcionalidades de hablar, escuchar, escribir y ver ... podríamos acoplarla a esta carcasa y utilizar dichas funcionalidades con ella también, es más, si el fabricante sacase posteriormente un nuevo dispositivo con las mismas funcionalidades podríamos acoplarlo y usarlo sin tener que actualizar nuestra carcasa, y si sacase uno con más funcionalidades (por ejemplo una cámara) pero tampoco quisiésemos actualizarla... podríamos usarlo también pero solo las funcionalidades que expone la nuestra, quedando el resto ocultas. Exacto, nos brinda una funcionalidad similar a la abstración de clases, pero con ventajas adicionales, como que esa PDA podría al mismo tiempo tener definidas funcionaliades para acoplarse a otras carcasas,  como a la de dispositivo GPS, a la de dispositivo de pantalla táctil ... ó incluso a una super-carcasa que fuese la suma de todas las demás.

 Una interfaz:

  • Es un contrato que asegura que la instancia que la implementa tiene definidos una serie de métodos y propiedades.
  • Es como una clase abstracta pero donde todos sus miembros son abstractos y públicos. 
  • No tiene campos porque no puede tener ningún tipo de estado, únicamente se pueden definir propiedades.
  • No tiene implementación, solo las definiciones de los métodos.
  • No puede heredar de una clase, pero si de una ó de múltiples interfaces (multiherencia).
  • No tiene ni constructores ni destructores.
  • Pueden ser especializadas por estructuras.

 Una interfaz se declara:

    interface IMovil
    {
        // Propiedades de solo lectura
        Botones Teclado { get;}
        Microfono Micro { get;}
        Altavoz Auricular { get;}
 
        // Propiedades de lectura/escritura
        ManosLibres MLBluethoot { get; set;}
 
        // Metodos
        void Hablar(Stream Voz);
        Stream Oir();
        void Teclear(char pulsacion);
    }

Y se implementa como la herencia:

    class NokiaN70 : IMovil
    {
        public Botones Teclado
        {
        }
 
        public Microfono Micro
        {
        }
 
        public Altavoz Auricular
        {
        }
 
        public ManosLibres MLBluethoot
        {
            get
            {
            }
            set
            {
            }
        }
 
        public void Hablar(Stream Voz)
        {
        }
 
        public Stream Oir()
        {
        }
 
        public void Teclear(char pulsacion)
        {
        }
    }

Para implementar varias se separan por comas:

    interface IMovil3G
    {
        void EnviarDatos(byte[] Datos);
        byte[] RecibirDatos();

}

 

    class NokiaN70 : IMovil, IMovil3G
    {
        public Botones Teclado
        {
        }
 
        public Microfono Micro
        {
        }
 
        public Altavoz Auricular
        {
        }
 
        public ManosLibres MLBluethoot
        {
            get
            {
            }
            set
            {
            }
        }
 
        public void Hablar(Stream Voz)
        {
        }
 
        public Stream Oir()
        {
        }
 
        public void Teclear(char pulsacion)
        {
        }
 
        public void EnviarDatos(byte[] Datos)
        {
        }
 
        public byte[] RecibirDatos()
        {
        }
    }

A una variable de tipo interfaz se le puede asignar una instancia que cumpla dicha interfaz y usar los métodos y propiedades definidos por ella.

            NokiaN70 MiMovil = new NokiaN70();
            byte[] b1 = MiMovil.RecibirDatos();
 
            IMovil im = MiMovil;
            im.Teclear('K');
            byte[] b2 = im.RecibirDatos(); // Error de compilación

Se puede comprobar si una instancia cumple una interfaz con el operador 'is':

            object MiMovil = new NokiaN70();
 
            if (MiMovil is IMovil3G)
            {
                IMovil3G im3g = MiMovil;
 
                byte[] b =im3g.RecibirDatos();
            }

No hay ambiguedad en la implementación de una interfaz, es decir, si dos interfaces definen un método del mismo nombre y firma no hay ambiguedad, pues simplemente se esta requiriendo que la instancia tenga ese método definido:

    interface INavegadorGPS
    {
        PantallaTactil Pantalla { get;}
        Stream Oir();
    }
 
    class NokiaN70 : IMovil, INavegadorGPS
    {
        public Botones Teclado
        {
        }
 
        public Microfono Micro
        {
        }
 
        public Altavoz Auricular
        {
        }
 
        public ManosLibres MLBluethoot
        {
            get
            {
            }
            set
            {
            }
        }
 
        public void Hablar(Stream Voz)
        {
        }
 
        public Stream Oir()
        {
        }
 
        public void Teclear(char pulsacion)
        {
        }
 
        // Solo se implementa este miembro, ya que ya existe un Oir()
        public PantallaTactil Pantalla
        {
        }
    }

Si la posible ambiguedad es en una reimplementación, pervalece el miembro de la interfaz derivada, aunque Visual Studio producirá un 'Warning' para que le pongamos el operador 'new', pero simplemente es indicatorio:

    class NokiaN70v2 : NokiaN70, INavegadorGPS
    {
        new PantallaTactil Pantalla
        {
        }
 
        new Stream Oir()
        {
        }
    }

Al implementar la interfaz, es posible declarar miembros de forma explícita, esto es, que solo serán accesibles si usamos la instancia a través de una variable de ese tipo de interfaz, no desde el tipo derivado:

    class NokiaN70 : INavegadorGPS
    {
        // Miembro declarado explicitamente
        Stream INavegadorGPS.Oir()
        {
        }
 
        // Miembro delcarado normalmente
        public PantallaTactil Pantalla
        {
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            NokiaN70 M = new NokiaN70();
            Console.WriteLine(M.Pantalla.ToString());
 
            // Asi no
            Console.WriteLine(M.Oir().ToString());
 
            // Asi si
            INavegadorGPS gps = M;
            Console.WriteLine(gps.Oir().ToString());
            
        }
    }

También se pueden usar para enmascarar instancias, ocultando los miembros que no queramos mostrar:

    class NokiaN70 : INavegadorGPS, IMovil3G, IMovil
    {
        // Implementación obviada.
 
        static public INavegadorGPS GetGPS()
        {
            return new NokiaN70();
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            INavegadorGPS gps = NokiaN70.GetGPS();
 
            gps.Oir();
 
            // Error
            gps.Teclear('K');
        }
    }

Espero haber sido medianamente claro :P En un próximo artículo hablaré de los genéricos y como con las interfaces podemos hacer aún más tipados nuestras clases y métodos genéricos.

Tags: ,

.NET 2.0 | C# 2.0

Comentarios

07/10/2007 20:39:06 #

pingback

Pingback from elbruno.com

Cliente FTP asincrono - vtortola

elbruno.com |

17/07/2008 4:30:19 #

trackback

Trackback from Pensando en asíncrono

Cargar un tipo dinamicamente

Pensando en asíncrono |

Comentarios no permitidos