tipos - ¿Cuándo debería elegir herencia sobre una interfaz cuando diseñe bibliotecas de clase C#?
que es una interfaz en programacion orientada a objetos (7)
Tengo un número de clases de Processor
que harán dos cosas muy diferentes, pero se llaman desde un código común (una situación de "inversión de control").
Me pregunto qué consideraciones de diseño debería tener en cuenta (o conocer, para ustedes, los US) al decidir si todas deberían heredar de BaseProcessor
, o implementar IProcessor
como una interfaz.
Aparte de la pureza del diseño, puede heredar solo una vez, por lo que si necesita heredar alguna clase de marco, la pregunta es irrelevante.
Si puede elegir, pragmáticamente puede elegir la opción que ahorra más tipeo. Por lo general, es la opción purista de todos modos.
EDITAR:
Si la clase base tendrá alguna implementación, esto podría ser útil, si es puramente abstracta entonces podría ser una interfaz.
Dado que los principios SOLID ofrecen más facilidad de mantenimiento y extensibilidad para su proyecto, preferiría las interfaces por herencia.
Además, si necesita agregar "funcionalidad adicional" a su interfaz, la mejor opción es crear una nueva interfaz completamente, siguiendo el I en SOLID, que es el principio de segmentación de interfaz.
Generalmente, la regla dice algo como esto:
- La herencia describe una relación is-a .
- La implementación de una interfaz describe una relación de cosas por hacer .
Para poner esto en términos algo más concretos, veamos un ejemplo. La clase System.Drawing.Bitmap
es una imagen (y, como tal, hereda de la clase Image
), pero también puede hacerlo , por lo que implementa la interfaz IDisposable
. También puede hacer serialización, por lo que implementa desde la interfaz ISerializable
.
Pero más prácticamente, las interfaces a menudo se usan para simular herencia múltiple en C #. Si su clase de Processor
necesita heredar de algo como System.ComponentModel.Component
, entonces no tiene más remedio que implementar una interfaz IProcessor
.
El hecho es que tanto las interfaces como la clase base abstracta proporcionan un contrato que especifica qué puede hacer una clase en particular. Es un mito común que las interfaces son necesarias para declarar este contrato, pero eso no es correcto. La mayor ventaja para mí es que las clases base abstractas le permiten proporcionar la funcionalidad predeterminada para las subclases. Pero si no hay una funcionalidad predeterminada que tenga sentido, no hay nada que le impida marcar el método en sí mismo como abstract
, lo que requiere que las clases derivadas lo implementen ellos mismos, al igual que si implementaran una interfaz.
Para obtener respuestas a preguntas como esta, a menudo me dirijo a las Pautas de diseño de .NET Framework , que tienen esto que decir sobre elegir entre clases e interfaces:
En general, las clases son la construcción preferida para exponer abstracciones.
El principal inconveniente de las interfaces es que son mucho menos flexibles que las clases cuando se trata de permitir la evolución de las API. Una vez que envía una interfaz, el conjunto de sus miembros se fija para siempre. Cualquier adición a la interfaz rompería los tipos existentes que implementan la interfaz.
Una clase ofrece mucha más flexibilidad. Puede agregar miembros a las clases que ya ha enviado. Siempre que el método no sea abstracto (es decir, siempre que proporcione una implementación predeterminada del método), las clases derivadas existentes seguirán funcionando sin cambios.
[. . . ]
Uno de los argumentos más comunes a favor de las interfaces es que permiten separar el contrato de la implementación. Sin embargo, el argumento asume incorrectamente que no se pueden separar los contratos de la implementación utilizando clases. Las clases abstractas que residen en un ensamble separado de sus implementaciones concretas son una gran manera de lograr dicha separación.
Sus recomendaciones generales son las siguientes:
- Favorece la definición de clases sobre las interfaces.
- Utilice clases abstractas en lugar de interfaces para desacoplar el contrato de las implementaciones. Las clases abstractas, si se definen correctamente, permiten el mismo grado de desacoplamiento entre el contrato y la implementación.
- Defina una interfaz si necesita proporcionar una jerarquía polimórfica de tipos de valores.
- Considere la posibilidad de definir interfaces para lograr un efecto similar al de la herencia múltiple.
Chris Anderson expresa particular acuerdo con este último principio, argumentando que:
Los tipos abstractos mejoran mucho la versión y permiten la extensibilidad futura, pero también queman el único tipo de base. Las interfaces son apropiadas cuando realmente se define un contrato entre dos objetos que es invariable a lo largo del tiempo. Los tipos de base abstractos son mejores para definir una base común para una familia de tipos.
Las interfaces son un "contrato", estas aseguran que algunas clases implementen un conjunto deseado de miembros: propiedades, métodos y eventos.
Las clases base (concretas o abstractas, no importa) son el arquetipo de alguna entidad. Esas son entidades que representan lo que es común en algunos físicos o conceptuales reales.
Cuándo usar las interfaces?
Cuando algún tipo necesita declarar que, al menos, tiene algunos comportamientos y propiedades que un consumidor debería preocuparse y usarlos para realizar alguna tarea.
Cuándo usar clases base (concretas y / o abstractas)
Cada vez que un grupo de entidades comparte el mismo arquetipo, es decir, B hereda A porque B es A con diferencias, pero B puede identificarse como A.
Ejemplos:
Hablemos de tablas.
Aceptamos tablas reciclables => Esto debe definirse con una interfaz como "IRecyclableTable" asegurando que todas las mesas reciclables tengan un método de "Reciclaje" .
Queremos tablas de escritorio => Esto debe definirse con herencia. Una "mesa de escritorio" es una "mesa". Todas las tablas tienen propiedades y comportamientos comunes y las de escritorio tendrán las mismas, agregando cosas que hacen que una mesa de escritorio funcione diferente a otros tipos de tablas.
Podría hablar sobre asociaciones, significado de ambos casos en un gráfico de objetos, pero en mi humilde opinión, si necesito dar argumentos en un punto de vista conceptual, respondería exactamente con esta argumentación.
No soy muy bueno en las opciones de diseño, pero si me lo piden, preferiré implementar una interfaz iProcessor si solo hay miembros para ampliar. Si hay otras funciones que no necesitan extenderse, heredar de baseprocessor es una mejor opción.
Ricardo,
¿Por qué ELEGIR entre ellos? Tendría una interfaz IProcessor como el tipo publicado (para usar en otro lugar del sistema); y si sucede que sus diversas implementaciones ACTUALES de IProcessor tienen un comportamiento común, entonces una clase abstracta de BaseProcessor sería un buen lugar para implementar ese comportamiento común.
De esta manera, si necesita un IProcessor en el futuro que NO haya sido para los servicios de BaseProcessor, NO TIENE que tenerlo (y posiblemente ocultarlo) ... pero los que lo quieren pueden tenerlo ... reduciendo en código / conceptos duplicados.
Solo mi humilde OPINIÓN.
Aclamaciones. Keith.
Si lo que elija no importa, siempre elija Interfaz. Permite más flexibilidad. Podría protegerlo de futuros cambios (si necesita cambiar algo en la clase base, las clases heredadas podrían verse afectadas). También permite encapsular los detalles mejor. Si está utilizando alguna Inversión de Control de Inyección de Dependencia, tienden a favorecer la interfaz.
O si la herencia no se puede evitar, probablemente sea una buena idea usarlos a ambos juntos. Cree una clase base abstracta que implemente una interfaz. En su caso, ProcessorBase implementa un IProcessor.
Similar a ControllerBase e IController en ASP.NET Mvc.