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!