2011-01-25 10 views
6

Vi un propuesto estándar de codificación de C# que decía "Trate de ofrecer una interfaz con todas las clases abstractas". ¿Alguien sabe la razón de esto?Interface/Abstract Class Coding Standard

+4

¿Puede proporcionar un enlace donde vio esta propuesta? –

+0

@Jay: Google me dirige aquí: http://docs.google.com/viewer?a=v&q=cache:reQ1URsy3X0J:www.danrigsby.com/Files/csharpcodingstandards.doc+%22Try+to+offer+an+interface + con + todos + Resumen + clases% 22 & hl = es & gl = us y pid = BL & Srcid = ADGEESi9ylZVo1InFp604CL926OHZ_QLr4taUT6O0DaojgsrS__V7ID4pUJw7gtdp5s0u0NwQYmAAjMILDxJsphtH_XmeFmYhunknO8UxD5il580OTtSD-yl94xtz0uw6VhUycN5eVvv y sig = AHIEtbQyKOvKQE69l_EVLO_3XNqJLeCKFw –

+0

y asumiendo las directrices he vinculado anteriormente son, de hecho, los mismos que usted lee, el primer punto señalado en el capítulo "Interfaces "la sección me molesta aún más:" Prefiero siempre interfaces sobre clases abstractas ". Y eso se recomienda incluso con más fuerza que la pauta que está citando. –

Respuesta

15

El .NET Framework Design Guidelines tiene algunas cosas interesantes que decir sobre interfaces y clases abstractas.

En particular, señalan que las interfaces tienen el mayor inconveniente de ser menos flexibles que las clases en lo que respecta a la evolución de una API. Una vez que envía una interfaz, sus miembros son fijos para siempre, y cualquier adición romperá la compatibilidad con los tipos existentes que implementan esa interfaz. Mientras que enviar una clase ofrece mucha más flexibilidad. Los miembros se pueden agregar en cualquier momento, incluso después de que se haya enviado la versión inicial, siempre que no sean abstractos. Cualquier clase derivada existente puede continuar sin cambios. La clase abstracta System.IO.Stream proporcionada en el Framework se da como un ejemplo. Inicialmente se envió sin soporte para agotar el tiempo de espera de las operaciones de E/S, pero la versión 2.0 pudo agregar miembros que admitían esta característica, incluso desde subclases existentes.

Por lo tanto, tener una interfaz correspondiente para cada clase base abstracta proporciona pocos beneficios adicionales. La interfaz no puede exponerse públicamente, o le quedan atrás al principio en términos de control de versiones. Y si solo expone la clase base abstracta, se gana poco al tener la interfaz en primer lugar.

Además, el punto a menudo se hace a favor de las interfaces que permiten separar el contrato de la implementación.Krzysztof Cwalina argumenta que esta afirmación es engañosa: asume incorrectamente que no se pueden separar los contratos de la implementación usando clases. Al escribir clases abstractas que residen en un ensamble separado de sus implementaciones concretas, es fácil lograr las mismas virtudes de separación. Él escribe:

A menudo escucho a la gente decir que las interfaces especifican los contratos. Creo que este es un mito peligroso. Las interfaces, por sí mismas, no especifican mucho más allá de la sintaxis requerida para usar un objeto. El mito de la interfaz como contrato hace que las personas hagan algo incorrecto al tratar de separar los contratos de la implementación, que es una gran práctica de ingeniería. Las interfaces separan la sintaxis de la implementación, lo cual no es tan útil, y el mito proporciona una falsa sensación de hacer la ingeniería correcta. En realidad, el contrato es semántico, y en realidad puede expresarse con cierta implementación.

En general, la directriz proporcionada es favorecen la definición de clases a través de interfaces. De nuevo, Krzysztof comenta:

En el transcurso de las tres versiones de .NET Framework, he hablado de esta guía con bastantes desarrolladores de nuestro equipo. Muchos de ellos, incluidos aquellos que inicialmente no estaban de acuerdo con la directriz, han dicho que lamentan haber enviado alguna API como interfaz. No he oído hablar de ningún caso en el que alguien lamentó haber enviado una clase.

Una segunda pauta argumenta que uno hace uso de clases abstractas en lugar de interfaces para desacoplar el contrato de implementaciones. El punto aquí es que las clases abstractas diseñadas correctamente todavía permiten el mismo grado de desacoplamiento entre el contrato y la implementación como interfaces. La perspectiva personal de Brian Pepin es así:

Una cosa que he empezado a hacer es hornear la mayor cantidad posible de contratos en mi clase abstracta. Por ejemplo, podría querer tener cuatro sobrecargas en un método donde cada sobrecarga ofrece un conjunto de parámetros cada vez más complejo. La mejor forma de hacerlo es proporcionar una implementación no virtual de estos métodos en la clase abstracta, y hacer que todas las implementaciones se encaminen hacia un método abstracto protegido que proporcione la implementación real. Al hacer esto, puede escribir toda la aburrida lógica de comprobación de argumentos una vez. Los desarrolladores que quieran implementar tu clase te lo agradecerán.

Quizás uno haría mejor revisando la promocionado frecuentemente "regla" que una clase derivada indica un relación es-un con la clase base, mientras que una clase que implementa una interfaz tiene una relación CAN-DO con esa interfaz. Para hacer el reclamo uno debe siempre codificar tanto una interfaz como una clase base abstracta, independientemente de las razones específicas para hacerlo, parece pasar por alto el punto.

+0

+1 Me gusta más tu explicación. Todavía estoy tratando de entender por qué Glav dijo que facilita las pruebas con Rhino. –

+0

@Harvey: Gracias. Realmente no sé lo que Glav quiere decir con eso, ya que nunca he usado ninguna de esas herramientas de prueba de burla. Es por eso que me aseguré de aclarar que podría haber excepciones basadas en razones específicas. –

3

sin mirar el artículo original, yo supongo que el autor original es lo que sugiere que para la capacidad de prueba y permitir una fácil burla de la clase con herramientas como MoQ, RhinoMocks etc.

+0

Glav tiene razón. Al menos con Rhino Mocks, probar métodos concretos es un PITA. Por lo tanto, tener la clase abstracta implementando una interfaz permite una capacidad de prueba mucho mayor. – Simone

0

Creo que es prematuro decir si una la interfaz es necesaria o no en un sentido general. Por lo tanto, creo que no deberíamos poner "Intentar ofrecer una interfaz con todas las clases abstractas" como un estándar de codificación a menos que ese estándar de codificación incluya más detalles sobre cuándo se aplica esta regla.

Si no voy a utilizar la interfaz en absoluto, ¿todavía tengo que definir una interfaz solo para cumplir con el estándar de codificación?

1

siempre I entiende Driven Design-Interface (IDD) para involucrar a los siguientes pasos en la creación de una clase concreta (en su forma más pura, para las clases no triviales):

  1. crear una interfaz para describir las propiedades y comportamientos que sus objetos deben exhibir, pero no cómo deberían funcionar.
  2. Cree una clase base abstracta como implementación principal de la interfaz. Implemente cualquier funcionalidad requerida por la interfaz, pero que es poco probable que difiera entre implementaciones concretas. También proporcione las implementaciones apropiadas por defecto (virtual) para miembros que es poco probable (pero posible) cambiar. También puede proporcionar constructores apropiados (algo que no es posible en el nivel de interfaz). Marque todos los otros miembros de la interfaz como abstractos.
  3. Crea tu clase concreta a partir de la clase abstracta, anulando un subconjunto de los miembros definidos originalmente por la interfaz.

El proceso anterior, si bien es largo, asegura la máxima adherencia al contrato que inicialmente establece, al tiempo que minimiza el código redundante en implementaciones alternativas.

Es por eso que, en general, asociaría una clase abstracta con una interfaz.

+0

No está claro cuál es la ventaja de una interfaz bajo este modelo. ¿Qué beneficios obtiene al crear la interfaz primero, en lugar de simplemente escribir la clase base abstracta como su implementación? –

+0

¡Si tan solo pudiéramos hacerlo de esa manera todo el tiempo! – XIVSolutions

+0

@Cody Es una actitud muy purista. Si acepta que la única (correcta) forma de dictar el comportamiento es a través de una interfaz, entonces, comenzar desde una clase abstracta se ve como un enfoque débil. Además, la interfaz presenta los miembros tal como están destinados a ser invocados, mientras que la clase abstracta los presenta como están destinados a ser implementados/anulados: una sutil diferencia en semántica. –

0

El desarrollo basado en pruebas (TDD) es una de las razones clave por las que desearía hacer esto. Si tiene una clase que depende directamente de su clase abstracta, no puede probarla sin escribir una subclase que pueda crearse una instancia en las pruebas de su unidad. Sin embargo, si su clase dependiente solo depende de una interfaz, puede proporcionar una "instancia" de esto fácilmente utilizando un marco de burla como Rhino Mocks, NMock, etc.

Creo que en última instancia será por la forma envías tu producto Solo enviamos binarios y los clientes nunca extienden nuestro trabajo. Internamente, tenemos interfaces para casi todo lo que las clases se pueden aislar por completo para la prueba unitaria.¡Esto ofrece enormes beneficios para las pruebas de refactorización y regresión!

EDIT: actualiza con el ejemplo

Considere siguiente código en una prueba de unidad:

// doesn't work - can't instantiate BaseClass directly 
var target = new ClassForTesting(new BaseClass());  

// where we only rely on interface can easily generate mock in our tests 
var targetWithInterface = new ClassForTestingWithInterface(MockRepository.GenerateStub<ISomeInterface>()); 

donde la versión abstracta clase es:

// dependent class using an abstract class 
public abstract class BaseClass 
{ 
    public abstract void SomeMethod(); 
} 

public class ClassForTesting 
{ 
    public BaseClass SomeMember { get; private set; } 

    public ClassForTesting(BaseClass baseClass) 
    { 
     if (baseClass == null) throw new ArgumentNullException("baseClass"); 
     SomeMember = baseClass; 
    } 
} 

y lo mismo pero utilizando la interfaz es:

public interface ISomeInterface 
{ 
    void SomeMethod(); 
} 

public abstract class BaseClassWithInterface : ISomeInterface 
{ 
    public abstract void SomeMethod(); 
} 

public class ClassForTestingWithInterface 
{ 
    public ISomeInterface SomeMember { get; private set; } 

    public ClassForTestingWithInterface(ISomeInterface baseClass) {...} 
} 
+0

¿Qué tal esto? Parece que puede simular una clase abstracta usando Rhino http://stackoverflow.com/questions/620894/mock-abstract-class-default-behaviour-with-rhino –

Cuestiones relacionadas