¿Cuándo usar clases abstractas en lugar de interfaces con métodos de extensión en C #?

70

"Clase abstracta" e "interfaz" son conceptos similares, siendo la interfaz la más abstracta de las dos. Un factor diferenciador es que las clases abstractas proporcionan implementaciones de métodos para clases derivadas cuando es necesario. Sin embargo, en C #, este factor diferenciador se ha reducido por la reciente introducción de métodos de extensión, que permiten que se proporcionen implementaciones para los métodos de interfaz. Otro factor diferenciador es que una clase puede heredar solo una clase abstracta (es decir, no hay herencia múltiple), pero puede implementar múltiples interfaces. Esto hace que las interfaces sean menos restrictivas y más flexibles. Entonces, en C #, ¿cuándo debemos usar clases abstractas en lugar de interfaces con métodos de extensión?

Un ejemplo notable del modelo de interfaz + método de extensión es LINQ, donde se proporciona la funcionalidad de consulta para cualquier tipo que se implemente a IEnumerabletravés de una multitud de métodos de extensión.

Gulshan
fuente
2
Y podemos usar 'Propiedades' en las interfaces también.
Gulshan
1
Suena como una pregunta específica de C # en lugar de una pregunta general de OOD. (La mayoría de los otros lenguajes OO no tienen métodos ni propiedades de extensión.)
Péter Török
44
Los métodos de extensión no resuelven el problema que resuelven las clases abstractas, por lo que la razón no es diferente de lo que es en Java u otro lenguaje similar de herencia única.
Trabajo
3
La necesidad de implementaciones de métodos de clase abstracta no ha sido reducida por los métodos de extensión. Los métodos de extensión no pueden acceder a campos de miembros privados, ni pueden declararse virtuales y anularse en clases derivadas.
zooone9243

Respuestas:

63

Cuando necesita que se implemente una parte de la clase. El mejor ejemplo que he usado es el patrón de método de plantilla .

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

Por lo tanto, puede definir los pasos que se tomarán cuando se llame a Do (), sin conocer los detalles de cómo se implementarán. Las clases derivadas deben implementar los métodos abstractos, pero no el método Do ().

Los métodos de extensiones no satisfacen necesariamente la parte de la ecuación "debe ser parte de la clase". Además, iirc, los métodos de extensión no pueden (parece ser) tener un alcance público.

editar

La pregunta es más interesante de lo que originalmente había dado crédito. Tras un examen más detallado, Jon Skeet respondió una pregunta como esta en SO a favor del uso de interfaces + métodos de extensión. Además, una desventaja potencial es usar la reflexión contra una jerarquía de objetos diseñada de esta manera.

Personalmente, tengo problemas para ver el beneficio de alterar una práctica común en la actualidad, pero también veo pocos o ningún inconveniente al hacerlo.

Cabe señalar que es posible programar de esta manera en muchos idiomas a través de clases de utilidad. Las extensiones solo proporcionan el azúcar sintáctico para que los métodos parezcan pertenecer a la clase.

Steven Evers
fuente
Gáneme a eso ..
Giorgi
1
Pero lo mismo se puede hacer con interfaces y métodos de extensión. Allí, los métodos que está definiendo en la clase base abstracta Do()se definirán como un método de extensión. Y los métodos que quedan para la definición de clases derivadas como DoThis()serán miembros de la interfaz principal. Y con las interfaces, puede soportar múltiples implementaciones / herencia. Y vea esto- odetocode.com/blogs/scott/archive/2009/10/05/…
Gulshan
@Gulshan: Porque una cosa se puede hacer de más de una manera no invalida la forma elegida. No hay ventaja en el uso de interfaces. La clase abstracta en sí es la interfaz. No necesita interfaces para soportar múltiples implementaciones.
ThomasX
Además, puede haber desventajas en el modelo de interfaz + extensión. Tome la biblioteca Linq, por ejemplo. Es genial, pero si necesita usarlo solo una vez en ese archivo de código, la biblioteca completa de Linq estará disponible en IntelliSense en CADA I Enumerable en su archivo de código. Esto puede ralentizar considerablemente el rendimiento de IDE.
KeithS
-1 porque no has demostrado ninguna forma en que las clases abstractas se ajusten mejor al Método de plantilla que las interfaces
Benjamin Hodgson
25

Se trata de lo que quieres modelar:

Las clases abstractas funcionan por herencia . Al ser clases base simplemente especiales, modelan algunos is-a -Relación.

Por ejemplo, un perro es un animal, entonces tenemos

class Dog : Animal { ... }

Como no tiene sentido crear realmente un animal genérico (¿cómo se vería?), Hacemos esta Animalclase base abstract, pero sigue siendo una clase base. Y la Dogclase no tiene sentido sin ser un Animal.

Las interfaces, por otro lado, son una historia diferente. No usan herencia, pero proporcionan polimorfismo (que también se puede implementar con herencia). No modelan una relación es-es , pero más bien sí es compatible .

Tomemos, por ejemplo IComparable, un objeto que admite ser comparado con otro .

La funcionalidad de una clase no depende de las interfaces que implementa, la interfaz solo proporciona una forma genérica de acceder a la funcionalidad. Todavía podríamos Disposede a Graphicso a FileStreamsin que se implementen IDisposable. La interfaz solo relaciona los métodos juntos.

En principio, uno podría agregar y eliminar interfaces como extensiones sin cambiar el comportamiento de una clase, simplemente enriqueciendo el acceso a ella. ¡Sin embargo, no puedes quitar una clase base ya que el objeto dejaría de tener sentido!

Darío
fuente
¿Cuándo elegirías mantener un resumen de clase base?
Gulshan
1
@Gulshan: Si lo hace, por alguna razón, no tiene sentido crear realmente una instancia de la clase base. Puedes "crear" un chihuahua o un tiburón blanco, pero no puedes crear un animal sin más especialización, por lo que harías un Animalresumen. Esto le permite escribir abstractfunciones para que sean especializadas por las clases derivadas. O más en términos de programación: probablemente no tenga sentido crear a UserControl, pero como a Button es a UserControl, entonces tenemos el mismo tipo de relación clase / clase base.
Dario
si tienes métodos abstractos, entonces tienes clase abstracta. Y métodos abstractos que tiene cuando no hay ningún valor para implementarlos (como "ir" para animales). Y, por supuesto, a veces no desea permitir objetos de alguna clase general, entonces lo hace abstracto.
Dainius
No existe una regla que diga que si es gramatical y semánticamente significativo decir "B es una A", entonces A debe ser una clase abstracta. Debes preferir las interfaces hasta que realmente no puedas evitar la clase. El código compartido se puede hacer a través de la composición de interfaces, etc. Hace que sea más fácil evitar pintarse en una esquina con árboles de herencia extraños.
sara
3

Me acabo de dar cuenta de que no he usado clases abstractas (al menos no creadas) en años.

La clase abstracta es una bestia algo extraña entre la interfaz y la implementación. Hay algo en las clases abstractas que hormiguea mi sentido de araña. Yo diría que uno no debe "exponer" la implementación detrás de la interfaz de ninguna manera (o hacer ningún vínculo).

Incluso si existe la necesidad de un comportamiento común entre las clases que implementan una interfaz, usaría una clase auxiliar independiente, en lugar de una clase abstracta.

Yo iría por las interfaces.

Maglob
fuente
2
-1: No estoy seguro de cuántos años tienes, pero es posible que quieras aprender patrones de diseño, particularmente el patrón del método de plantilla. Los patrones de diseño te harán un mejor programador y terminarán con tu ignorancia de las clases abstractas.
Jim G.
Lo mismo sobre la sensación de hormigueo. Las clases, la primera y principal característica de C # y Java, son una característica para crear un tipo y encadenarlo inmediatamente para una sola implementación.
Jesse Millikan
2

En mi humilde opinión, creo que hay una mezcla de conceptos aquí.

Las clases usan un cierto tipo de herencia, mientras que las interfaces usan un tipo diferente de herencia.

Ambos tipos de herencia son importantes y útiles, y cada uno tiene sus pros y sus contras.

Empecemos desde el principio.

La historia con interfaces comienza mucho tiempo atrás con C ++. A mediados de la década de 1980, cuando se desarrolló C ++, la idea de las interfaces como un tipo diferente de tipo aún no se realizó (llegaría a tiempo).

C ++ solo tenía un tipo de herencia, el tipo de herencia utilizada por las clases, llamada herencia de implementación.

Para poder aplicar la recomendación de GoF Book: "Para programar para la interfaz y no para la implementación", C ++ admite clases abstractas y herencia de implementación múltiple para poder hacer lo que las interfaces en C # son capaces (¡y útiles!) De hacer .

C # presenta un nuevo tipo de tipo, interfaces y un nuevo tipo de herencia, herencia de interfaz, para ofrecer al menos dos formas diferentes de soportar "Programar para la interfaz y no para la implementación", con una advertencia importante: C # solamente admite herencia múltiple con herencia de interfaz (y no con herencia de implementación).

Entonces, las razones arquitectónicas detrás de las interfaces en C # es la recomendación de GoF.

Espero que esto pueda ser de ayuda.

Saludos cordiales, Gastón

Gastón E. Nusimovich
fuente
1

La aplicación de métodos de extensión a una interfaz es útil para aplicar un comportamiento común en las clases que pueden compartir solo una interfaz común. Dichas clases pueden existir y estar cerradas a extensiones por otros medios.

Para nuevos diseños, preferiría el uso de métodos de extensión en las interfaces. Para una jerarquía de tipos, los métodos de extensión proporcionan un medio para extender el comportamiento sin tener que abrir toda la jerarquía para su modificación.

Sin embargo, los métodos de extensión no pueden acceder a la implementación privada, por lo tanto, en la parte superior de una jerarquía, si necesito encapsular la implementación privada detrás de una interfaz pública, entonces una clase abstracta sería la única forma.

Ed James
fuente
No, no es la única forma segura. Hay otra forma más segura. Esos son los métodos de extensión. Los clientes no se infectarán en absoluto. Por eso he mencionado interfaces + métodos de extensión.
Gulshan
@ Ed James, creo que "menos poderoso" = "más flexible" ¿Cuándo elegirías una clase abstracta más poderosa en lugar de una interfaz más flexible?
Gulshan
@Ed James In asp.mvc se utilizaron métodos de extensión desde el principio. ¿Qué piensas sobre eso?
Gulshan
0

Creo que en C # no se admite la herencia múltiple y por eso se introduce el concepto de interfaz en C #.

En el caso de las clases abstractas, tampoco se admite la herencia múltiple. Por lo tanto, es fácil decidir si utilizar clases o interfaces abstractas.

Principiante
fuente
Para ser precisos, se trata de clases abstractas puras que se llaman "interfaces" en C #.
Johan Boulé
-1

Estoy en el proceso de implementar una clase abstracta en mi proyecto simplemente porque C # no admite operadores (conversiones implícitas, en mi caso) en las interfaces.

palswim
fuente