c# - una - qué diferencia hay entre clases abstractas e interfaces
¿Diferencia entre interfaz y clase abstracta en términos de desacoplamiento? (9)
Como sabemos, hay básicamente dos diferencias importantes entre la interfaz y la clase abstracta.
Podemos tener definiciones de funciones en clase abstracta. Esto es una ventaja cuando queremos agregar una función en una clase sin necesidad de buscar todas las implementaciones.
Podemos tener una implementación de interfaz múltiple.
¿Acabo de enterarme de que podemos diferenciarlos en términos de desacoplamiento?
Tus comentarios...
Además, si puede, ¿puede proporcionar un enlace muy básico que explique el desacoplamiento para la clase Interfaz y Resumen?
Normalmente utilizamos Business Logic Layer , Data Access Layer (contiene funciones abstractas) y DataAccess.SqlServer Layer . ¿Derecha? A pesar de que conocemos las necesidades de la empresa, ¿por qué creamos la capa de acceso a los datos (contiene funciones abstractas), por qué la capa de lógica de negocios no puede acceder directamente a la capa de DataAccess.SqlServer ?
No voy a discutir cuáles son los pros / contra de estos dos constructos en general, ya que hay suficientes recursos en eso.
Sin embargo, en términos de ''desacoplamiento'' de un componente de otro, la herencia de interfaz es mucho mejor que las clases abstractas o la herencia de clase en general (De hecho, no creo que ser abstracto o no sea muy diferente en términos de desacoplamiento como todo abstract
es evitar que la clase sea instanciada sin una implementación concreta).
La razón del argumento anterior es que las interfaces le permiten reducir la exposición al mínimo absoluto de lo que requiere el "componente dependiente", si requiere una interfaz de método único puede hacerlo fácilmente, o incluso puede ser una interfaz de marcador sin ningún método. Esto podría ser difícil con una clase base (abstracta u concreta) ya que debería implementar toda la funcionalidad ''común'' para esa base. Debido a esto, un componente que depende del "tipo de base" automáticamente "verá" toda la funcionalidad común, incluso si no la necesita para sus propósitos.
Las interfaces también le brindan la mejor flexibilidad, ya que incluso las clases heredadas de bases que no tienen nada en común, pueden implementar una interfaz y ser utilizadas por el componente que espera esa interfaz. Un buen ejemplo de esto es la interfaz IDisposable
.
Por lo tanto, mi conclusión es para desacoplar la preocupación de que todos sus componentes dependan de interfaces que tipos básicos, y si encuentra que la mayoría de las clases que implementan esa interfaz tiene una implementación común, entonces tenga una clase base implementando esa interfaz y herede otras clases de esa base.
La diferencia principal es esta:
Las interfaces exponen cero o más firmas de métodos que todos los descendientes deben implementar (de lo contrario, el código ni siquiera se compilará). Los métodos expuestos a la interfaz se pueden implementar implícitamente (cada tipo derivado de la interfaz tiene acceso a ellos) o explícitamente (se puede acceder a los métodos solo si el objeto se encasilla en el tipo de interfaz). Se pueden encontrar más detalles y un ejemplo en esta pregunta .
Las clases abstractas exponen cero o más métodos completos, que los descendientes pueden usar o anular, proporcionando su propia implementación. Este enfoque le permite definir un comportamiento personalizable y "predeterminado". Las clases abstractas le permiten agregar fácilmente nuevos métodos sin problemas (
NotImplementedException
realmente brilla al agregar métodos a clases abstractas), mientras que agregar un método a una interfaz requiere que modifique todas las clases que lo implementan.
El punto final es que una clase puede implementar más de una interfaz simultáneamente. Algunos ejemplos del mundo real podrían ser:
- Un disco duro que proporciona puertos USB y LAN es una buena demostración de la herencia de múltiples interfaces
- Una computadora portátil que tiene un LED marcado como "bluetooth" pero no tiene hardware bluetooth a bordo es una buena analogía del concepto de no implementar un método abstracto (tienes el LED, tienes el pequeño símbolo B, pero no hay nada debajo del techo).
Editar 1
Aquí hay un enlace de MSDN que explica cómo elegir entre la interfaz y las clases.
Desacoplamiento
En la programación y el diseño, este es generalmente el acto de hacer código que se puede reutilizar con el menor número de dependencias posible.
Patrón de fábrica en este contexto
Al utilizar el patrón de fábrica, tiene una fábrica centralizada que puede crear objetos sin necesariamente definirlos. Eso dependería de la definición del objeto.
Resumen e interfaz
Interfaz
Definir una interfaz es la mejor práctica, ya que permite utilizar un tipo de peso ligero para la inferencia y también proporciona un modelo que todas las clases heredantes deben cumplir. Por ejemplo, IDisposable
debe implementar el método Dispose
. Tenga en cuenta que esto está desacoplado de la interfaz, ya que cada clase que hereda IDisposable
definirá su propia función del método Dispose
.
Abstracto
El resumen es similar a la interfaz en que se usa para herencia e inferencia, pero contiene definiciones que todas las clases heredarán. Algo en la medida de cada automóvil tendrá un motor, por lo que una buena clase abstracta para automóviles podría incluir un conjunto predefinido de métodos para un motor.
Editar
Explicación
Aquí verá un ejemplo simple de herencia usando una interfaz y una clase abstracta. El desacoplamiento ocurre cuando la interfaz es heredada por una clase abstracta y luego sus métodos son personalizados. Esto permite que una clase herede la clase abstracta y aún tenga el mismo tipo que la interfaz. La ventaja es que la clase que hereda la clase abstracta se puede usar cuando el tipo esperado es la interfaz original.
Desacoplamiento
Esa ventaja permite que se use cualquier implementación que se ajuste a la interfaz esperada. Como tal, muchas sobrecargas diferentes pueden escribirse y pasarse. Aquí hay un ejemplo de una.
Ejemplo
Definición de interfaz
public interface IReady
{
bool ComputeReadiness();
}
Herencia
public abstract class WidgetExample : IReady
{
public int WidgetCount { get; set; }
public int WidgetTarget { get; set; }
public bool WidgetsReady { get; set; }
public WidgetExample()
{
WidgetCount = 3;
WidgetTarget = 45;
}
public bool ComputeReadiness()
{
if (WidgetCount < WidgetTarget)
{
WidgetsReady = false;
}
return WidgetsReady;
}
}
public class Foo : WidgetExample
{
public Foo()
{
this.WidgetTarget = 2;
}
}
public class Bar : IReady
{
public bool ComputeReadiness()
{
return true;
}
}
Desacoplamiento
public class UsesIReady
{
public bool Start { get; set; }
public List<string> WidgetNames { get; set; }
//Here is the decoupling. Note that any object passed
//in with type IReady will be accepted in this method
public void BeginWork(IReady readiness)
{
if (readiness.ComputeReadiness())
{
Start = true;
Work();
}
}
private void Work()
{
foreach( var name in WidgetNames )
{
//todo: build name
}
}
}
Polimorfismo
public class Main
{
public Main()
{
//Notice that either one of these implementations
//is accepted by BeginWork
//Foo uses the abstract class
IReady example = new Foo();
UsesIReady workExample = new UsesIReady();
workExample.BeginWork(example);
//Bar uses the interface
IReady sample = new Bar();
UsesIReady workSample = new UsesIReady();
workSample.BeginWork(sample);
}
}
Definir un contrato usando una clase abstracta significa que sus implementadores deben heredar de esta clase abstracta. Como C # no es compatible con la herencia múltiple, estos implementadores no podrán tener una jerarquía de clases alternativa, lo que puede ser bastante limitante para algunos. En otras palabras, una clase abstracta básicamente roba al implementador de la función de jerarquía de clases, que a menudo se necesita para obtener o usar algunas otras capacidades (de un marco de trabajo o biblioteca de clases).
La definición de un contrato utilizando una interfaz deja la jerarquía de clases libre para que los implementadores la usen de la forma que les parezca, en otras palabras, brindando mucha más libertad de implementación.
Desde una perspectiva de los criterios de evaluación, cuando hablamos de acoplamiento aquí podemos hablar de las preocupaciones de tres autores separables, el cliente usa (llama) la API / contrato, el definidor de la API / contrato y el implementador de la API / contrato ; podemos hablar de libertad (cuantas menos restricciones haya, mejor), encapsulación (cuanto menos conciencia se necesita, mejor) y resiliencia frente al cambio.
Yo ofrecería que una interfaz resulta en un acoplamiento más flexible que una clase abstracta, en particular, entre el definidor y el implementador, debido a la mayor libertad que ofrece el implementador.
Por otro lado, cuando se trata de versiones, al menos se puede agregar otro método a la clase abstracta sin necesidad de actualizar las implementaciones de las subclases, siempre que el método agregado tenga una implementación en la clase abstracta. Las interfaces de versiones a través de los límites de la DLL generalmente implican agregar otra interfaz, mucho más compleja de implementar. (Por supuesto, esto no es una preocupación si puede refactorizar todas las implementaciones juntas (por ejemplo, porque están todas en la misma DLL)).
Desacoplamiento por el desacoplamiento es un ejercicio inútil.
Las interfaces están destinadas a ser utilizadas para la integración donde no se requiere que los detalles sean conocidos (por ejemplo SendEmail ()). Los usos comunes incluyen componentes, servicios, repositorios y como marcadores para IOC y las implementaciones genéricas.
Los métodos de extensión con restricciones de tipo genérico que incluyen interfaces permiten una funcionalidad similar a los rasgos encontrados en Scala con capacidad de compilación similar.
public interface IHasQuantity { double Quantity { get; } }
public interface IHasPrice { decimal PricePerUnit { get; } }
public static class TraitExtensions
{
public static decimal CalculateTotalPrice<T>(this T instance)
where T : class, IHasPrice, IHasQuantity
{
return (decimal)instance.Quantity * instance.PricePerQuantity;
}
}
En mi opinión, las clases abstractas y la herencia de clases se usan en exceso.
Los principios de diseño SÓLIDOS nos enseñan que el principio de sustitución de Liskov implica que la herencia de clase solo debe usarse si la clase heredada es sustituible por el antepasado. Esto significa que todos los métodos deben implementarse (no arrojar nuevos NotImplementedExeption ()) y deben comportarse como se espera.
Personalmente, he encontrado la herencia de clase útil en el caso del patrón de Método de plantilla, así como para las máquinas de estado. Los patrones de diseño, como el patrón de construcción, en la mayoría de los casos son más útiles que las cadenas profundas de herencia.
Ahora volvamos a tu pregunta; las interfaces se deben usar la mayoría si no todo el tiempo. La herencia de clases se debe usar internamente y solo externamente para propósitos de definición, después de lo cual se debe usar una interfaz para la interacción y la implementación concreta proporcionada a través de una fábrica o para ser inyectada a través de un contenedor de IOC.
Idealmente, cuando se usan bibliotecas externas, se debe crear una interfaz y se debe implementar un adaptador para exponer solo la funcionalidad requerida. La mayoría de estos componentes se pueden configurar de antemano o en tiempo de ejecución para resolverlos a través de un contenedor IOC.
En términos de desacoplamiento, es importante desacoplar la aplicación de sus implementaciones (especialmente las dependencias externas) para minimizar las razones para el cambio.
Espero que mi explicación te indique la dirección correcta. Recuerde que es preferible refactorizar las implementaciones que funcionan y, a partir de entonces, las interfaces se definen para exponer la funcionalidad.
Las clases e interfaces abstractas no son opciones MUTUAMENTE EXCLUSIVAS. A menudo defino tanto una interfaz como una clase abstracta que implementa esa interfaz.
La interfaz asegura el máximo desacoplamiento porque no obliga a su clase a pertenecer a una jerarquía de herencia específica, por lo que su clase puede heredar de cualquier otra clase. En otros términos, cualquier clase puede heredar de una interfaz, mientras que las clases que ya heredan de otras clases no pueden heredar de una clase abstracta.
Por otro lado, en una clase abstracta puede factorizar el código que es común a todas las implementaciones, mientras que con las interfaces se ve obligado a implementar todo desde el principio. Como conclusión, a menudo la mejor solución es usar TANTO una clase abstracta como una Interfaz, de modo que se puede pasar de reutilizar el código común contenido en la clase abstracta, si es posible, a volver a implementar la interfaz desde cero, si es necesario .
He estado revisando las respuestas, y todas parecen un poco complicadas para la pregunta. Así que aquí está mi (afortunadamente) respuesta más simple.
- La interfaz debe usarse cuando ninguno de los detalles de implementación están disponibles para el alcance actual del código.
- Los resúmenes deben usarse cuando algunos de los detalles de implementación están disponibles para usted
- Y, para completar, cuando todos los detalles de implementación estén disponibles, debería usar clases .
En términos de desacoplamiento, aunque estoy un tanto de acuerdo con Shelakel, a los fines de esta pregunta, y al indicar las prácticas de diseño totalmente desacopladas, sugeriría lo siguiente:
- Siempre use interfaces para definir el comportamiento externo.
- Cuando tenga algunos de los detalles de implementación disponibles, use clases abstractas para definirlos, pero implemente las interfaces en las clases abstractas , y herede esas clases sucesivamente.
Esto garantiza que más adelante si necesita cambiar algunos detalles de implementación poco claros en una nueva implementación, puede hacerlo sin modificar la clase abstracta existente, y también puede agrupar diferentes tipos de implementación en diferentes clases abstractas.
EDITAR: Olvidé incluir el enlace :) http://www.codeproject.com/Articles/11155/Abstract-Class-versus-Interface
La codificación de la interfaz proporciona reutilización y polimorfismo. En la medida en que la clase implemente la interfaz, la interfaz o clase abstracta se puede pasar al parámetro en lugar de a la clase que implementa la interfaz. Su problema técnico común se maneja vis diseño de interfaz y clase abstracta e implementándolo y dando subclase de la implementación de la funcionalidad específica. Imagine que es como el marco. El marco define la interfaz y la clase abstracta e implementarla que es común a todos. Y aquellos que son abstractos son implementados por el cliente de acuerdo con sus propios requisitos.
public interface Polymorphism{
void run();
Void Bark();
Energy getEnergy(Polymorphism test);
Public abstract class EnergySynthesis implements Polymorphism{
abstract void Energy();
Void Bark(){
getEnergy(){
}
void run(){
getEnergy();
}public EnegyGeneartion extends EnergySynthesis {
Energy getEnergy(Polymorphism test){
return new Energy( test);
}
MainClass{
EnegyGeneartion test=new EnegyGeneartion ();
test.getEnergy(test);
test.Bark()
.
.
.
.
.
//here in Energy getEnergy(Polymorphism test) any class can be passed as parameter that implemets interface
La mejor manera de entender y recordar la diferencia entre la interfaz y la clase abstracta , es recordar que la clase abstracta es una clase normal y puedes hacer todo lo posible con la clase abstracta que puedes hacer con la clase normal con dos excepciones .
- No puedes instanciar una clase abstracta
- Puedes tener el método abstracto solo en clase abstracta