Clases abstractas VS Interfaces + métodos de extensión en C#

El uso de clases abstractas y herencia para organizar la lógica de nuestras aplicaciones se puede sustituir o complementar con interfaces y métodos de extensión usando C#. En este artículo veremos un ejemplo de ambas aproximaciones así como sus ventajas e inconvenientes.

Clases Abstractas

Las clases abstractas nos permiten tener una clase base con cierta funcionalidad común ya implementada, sobre la que podemos heredar y especificar algunos métodos. Para este ejemplo, la clase Transporte implementa el método Mover, y las clases Coche y Bicicleta especifican el número de ruedas.

public abstract class Transporte
{
    public sealed void Mover()
    {
        Console.WriteLine("Moviendo {0} ruedas", Ruedas);
    }

    public abstract int Ruedas { get; }
}

public class Coche : Transporte
{
    public override int Ruedas
    {
        get { return 4; }
    }
}

public class Bicicleta : Transporte
{
    public override int Ruedas
    {
        get { return 2; }
    }
}

En el caso de que el método no pudiera ser reimplementado por clases hijas, debemos usar el indicador sealed como se muestra. De esta manera las clases hijas no podrán sobreescribir esta funcionalidad. Lo que tenemos aquí es un sistema «ES-UN», es decir, una bicicleta es un transporte, y un coche es un transporte. Esta relación se basa en la herencia, y solo se permite heredar de una única clase en C#.

El hecho de poder heredar de solamente una clase nos puede generar acoplamiento y la necesidad de otro tipo de alternativas cuando la única opción que tenemos es heredar de una clase específica.

Una alternativa al uso de clases abstractas viene dado por las interfaces y los métodos de extensión:

Interfaces

A diferencia de las clases abstractas, una interfaz por sí sola no aporta funcionalidad, sino que fija un contrato que pueden implementar de manera distinta otras clases.

Por otra parte, un método de extensión nos permite agregar funcionalidad a una clase, y es en lo que se basan componentes como Linq. Si combinamos ambas características, podemos obtener un comportamiento similar como se muestra a continuación:

static class ITRansporteExtensions
{
    public static void Mover(this ITransporte transporte)
    {
        Console.WriteLine("Moviendo {0} ruedas", transporte.Ruedas);
    }
}

public interface ITransporte
{
    public abstract int Ruedas { get; }
}

public class Coche : ITransporte
{
    public override int Ruedas
    {
        get { return 4; }
    }
}

public class Bicicleta : ITransporte
{
    public override int Ruedas
    {
        get { return 2; }
    }
}

Cuando combinamos una interfaz con un método de extensión, dotamos de métodos a clases e interfaces que originalmente no tenían. Como ventaja adicional está el hecho de que podemos implementar varias interfaces al mismo tiempo, con lo cual podríamos acceder a los métodos de extensión de todos ellos.

La desventaja de usar métodos de extensión es que como no estamos accediendo a los campos internos de la clase, solamente vamos a tener acceso a la interfaz que se defina para nosotros.

Conclusiones

Ambas características nos permiten llegar a un resultado común, si bien el uso de clases abstractas nos permite un diagrama de herencia mas claro, en ocasiones podemos optar a una alternativa más abierta. Los métodos extensores no son mas que, como dicen los anglosajones «syntactic sugar», es decir, una manera mas cómoda de llamara a un método estático que procesa una interfaz.

¿Y tu, usas clases abstractas o interfaces?

Actualización: Gracias a Sergio por el comentario, en efecto, el indicador sealed nos dice que el método no puede ser reimplementado o sobresscrito.
Actualización 2: Gracias Anti por un par de correcciones de estilo!

Autor: Roberto Luis Bisbé

Software Developer, Computer Engineer

8 opiniones en “Clases abstractas VS Interfaces + métodos de extensión en C#”

  1. Por defecto uso clases abstractas.
    Mis ventajas: es un único artefacto en vez de dos, con lo que está todo en un mismo sitio, lo que lo hace más fácil de mantener; permite acceder a los field privados de la clase sin tener que abrirlos (aunque fuera internal o readonly) para verlos desde el método extensor; también permite definir métodos protected compartidos en la jerarquía que no se vean desde fuera…
    ¿Entonces cuándo usar la interfaz? Cuando tienes algún problema como la herencia múltiple que te impide montar la jerarquía simple.

  2. Hay una parte del comienzo que me confunde un poco.. y es la siguiente:
    «En el caso de que el método no pudiera ser heredado, debemos usar el indicador sealed como se muestra.»
    No es que no se pueda heredar, que sí que se hereda. Lo que no se puede es sobreescribir (pues está sellado, «sealed»). ¿O me he perdido algo? xD

    Gran artículo… Necesario cuando una única rama de herencia es insuficiente y necesitas combinar diferentes antecesores: herencia múltiple.

  3. Muy interesante Roberto.
    Evidentemente la discusión sobre clase abstracta, interfaz o esta combinación tan interesante que planteas tu aquí daría para mucho que hablar.
    Yo de entrada prefiero la interfaz; que nos permite una mayor flexibilidad. Si se necesita implementación común optaría por la clase abstracta. En adelante tendré también en consideración también esta propuesta mixta que haces interfaz-extensión.

    Por ponerte alguna pega :o( me gustaría invitarte a leer un post que escribí sobre el principio de sustitución de Liskov. En el trato de explicar entre otras cosas por que según este principio la relación de herencia no debería nominarse como «ES-UN» sino más bien «SE-COMPORTA-COMO-UN» ya que la relación de herencia debe tener un sentido funcional. En tu ejemplo, la clase «Barco» NO debe heredar de Transporte; aunque barco «ES-UN» Transporte no «SE-COMPORTA-COMO-UN» Transporte (no mueve ruedas).
    Bueno este es el link al post por si te apetece echar un vistazo: http://designcodetips.blogspot.com.es/2013/10/the-liskovs-substitution-principle-lsp.html

    De nuevo te felicito por el post. Muy interesante la alternativa planteada.
    Gracias por compartirlo

  4. Hola,.muy buena explicación, en verdad uso las tres cosas que comentas creo que ninguna es mejor que otra, depende del caso puntual, sin embargo el uso de interfaces va muy bien con.algún DiI Container, pero más allá lo importante es tener código desacoplado y mantenible.

    Saludos!

  5. Muy buen artículo.
    Desde mi punto de vista no hay una cosa mejor que otra, sino que dependiendo de lo que se desee hacer hay que usar una Interfaz o una Clase Abstracta. Yo lo llevo más al plano de «Cómo debe ser (clase abstracta)», «Qué tiene que hacer (interfaz)»

  6. Creo que no es cuestión de decidir si herencia o interfaces, sino de elegir la abstracción correcta. Mi recomendación es utilizar el esquema Red Green Refactor de TDD. No diseño herencias ni interfaces a priori; simplemente programo hasta que veo que necesito reutilizar código o acceder a una funcionalidad que se puede realizar de distintas maneras. ¿Eres capaz de identificar código común que usan varias clases? Herencia. ¿Existe una funcionalidad que se puede realizar de distintas formas? Interfaz.

    Cuando me encuentro con que algo se podría modificar y mejorar, la respuesta surge casi instantánea. Escribir código decidiendo a priori si usar herencia o interfaces es propenso a errores y al final no acabas haciendo lo que inicialmente diseñaste.

    Lo veo así de simple.

Replica a Julio Avellaneda Cancelar la respuesta

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.