2012-07-04 20 views
13

Duplicar posible:
Interface vs Base class¿Ventajas de usar la interfaz sobre la clase abstracta para el patrón de repositorio?

Su común ver el patrón repositorio implementado utilizando Interfaces

public interface IFooRepository 
{ 
    Foo GetFoo(int ID); 
} 

public class SQLFooRepository : IFooRepository 
{ 
    // Call DB and get a foo 
    public Foo GetFoo(int ID) {} 
} 

public class TestFooRepository : IFooRepository 
{ 
    // Get foo from in-memory store for testing 
    public Foo GetFoo(int ID) {} 
} 

pero que podría ser igualmente esto utilizando las clases abstractas.

public abstract class FooRepositoryBase 
{ 
    public abstract Foo GetFoo(int ID); 
} 

public class SQLFooRepository : FooRepositoryBase 
{ 
    // Call DB and get a foo 
    public override Foo GetFoo(int ID); {} 
} 

public class TestFooRepository : FooRepositoryBase 
{ 
    // Get foo from in-memory store for testing 
    public override Foo GetFoo(int ID); {} 
} 

¿Cuáles son las ventajas específicas de la utilización de una interfaz a través de una clase abstracta en un escenario repositorio?

(es decir, no sólo dime que se puede implementar múltiples interfaces, ya que esto - ¿por qué hacer eso en un repositorio de aplicación)

Editar para aclarar - páginas como " MSDN - Choosing Between Classes and Interfaces "puede parafrasearse como 'Elija clases más de las interfaces a menos que haya una buena razón para no' - ¿Cuáles son las buenas razones en el caso específico de un patrón Repositorio

+0

no afecta el uso de una interfaz a prevenir el boxeo y la ineficiencia esto trae? – Liam

+1

Una de las opciones de diseño. –

+0

@Pranay: ¿a dónde va tu respuesta? : – Ryan

Respuesta

3

La principal ventaja de usar una interfaz sobre una clase abstracta en esta instancia es que la interfaz es completamente transparente: esto es más un problema donde no tiene acceso al origen de la clase que está heredando .

Sin embargo, esta transparencia le permite producir pruebas unitarias de un alcance conocido: si prueba una clase que acepta una interfaz como parámetro (usando el método de inyección de dependencia), sabrá que está probando la clase con un conocido cantidad; la implementación de prueba de la interfaz solo contendrá su código de prueba.

De manera similar, cuando prueba su repositorio, sabe que está probando solo su código en el repositorio. Esto ayuda a limitar el número de posibles variables/interacciones en la prueba.

2

Mientras que otros pueden tener más que añadir, a partir de una puramente práctico Desde el punto de vista, la mayoría de los marcos de IoC funcionan mejor con interfaz -> mapeos de clase. Puede tener diferentes visibilidades en sus interfaces & clases, mientras que con la herencia, las visibilidades deben coincidir.

Si usted no está utilizando un marco COI, desde mi punto de vista no hay ninguna diferencia. Los proveedores se basan en clases base abstractas.

4

Personalmente, tienden a tener una interfaz que sostiene la firma para los métodos que son puramente "business-relacionadas", por ejemplo Foo GetFoo(), void DeleteFood(Foo foo), etc. I también tienen una clase abstracta genérico que asimientos métodos protegida como T Get() o void Delete(T obj) .

guardo mis métodos protegidos en la clase abstracta Repository para que el mundo exterior no es consciente de la fontanería (Repository se verá como object), pero sólo del modelo de negocio a través de la interfaz.

Además de tener la fontanería compartió otra ventaja es que no tengo por ejemplo una Delete método (protected) a disposición de cualquier repositorio, pero no es público, por lo que no veo obligado a implementarlo en un repositorio donde tiene ningún negocio es decir, eliminar algo de mi fuente de datos.

public abstract class Repository<T> 
{ 
    private IObjectSet objectSet; 

    protected void Add(T obj) 
    { 
     this.objectSet.AddObject(obj); 
    } 

    protected void Delete(T obj) 
    { 
     this.objectSet.DeleteObject(obj); 
    } 

    protected IEnumerable<T>(Expression<Func<T, bool>> where) 
    { 
     return this.objectSet.Where(where); 
    } 
} 

public interface IFooRepository 
{ 
    void DeleteFoo(Foo foo); 
    IEnumerable<Foo> GetItalianFoos(); 
} 

public class FooRepository : Repository<Foo>, IFooRepository 
{ 
    public void DeleteFoo(Foo foo) 
    { 
     this.Delete(foo); 
    } 

    public IEnumerable<Foo> GetItalianFoos() 
    { 
     return this.Find(foo => foo.Country == "Italy"); 
    } 
} 

La ventaja de utilizar la clase abstracta a través de una interfaz para la fontanería es que mis repositorios concretos no tienen que poner en práctica el método que no es necesario (o DeleteAdd por ejemplo) pero están a su disposición si lo necesitan. En el contexto actual, no hay motivo comercial para algunos Foos, por lo que el método no está disponible en la interfaz.

La ventaja de utilizar una interfaz sobre una clase abstracta para el modelo de negocio es que la interfaz proporciona las respuestas a cómo tiene sentido manipular Foo desde un punto de vista comercial (¿tiene sentido eliminar algunos focos? Para crear ? etc.). También es más fácil usar esta interfaz cuando se realizan pruebas unitarias. El resumen Repository que uso no se puede probar en unidades porque generalmente está estrechamente acoplado con la base de datos. Solo se puede probar en pruebas de integración. Usar una clase abstracta para el propósito comercial de mis repositorios me impediría usarlos en pruebas unitarias.

+0

¿Pero por qué? la implementación (plomería) se puede ocultar con cualquiera de los métodos. – Ryan

+0

Plumbery es compartida por todos los repositorios (usando la misma fuente de datos). Entonces todos los repositorios heredan de la misma clase abstracta de 'Repository'. La fontanería está escrita una vez. La interfaz cumple un propósito comercial específico. Se implementó solo el repositorio que maneja este propósito. Por lo general, no tiene 50 fuentes de datos pero puede tener 50 propósitos comerciales. Es mi forma de hacerlo porque creo que es práctico y limpio, pero como todo, hay varias formas de hacerlo. – Guillaume

+0

Entonces, si entiendo de su ejemplo de eliminación, ¿es que dice que prefiere clases abstractas sobre interfaces para un repositorio? – Ryan

2

Supongo que la diferencia clave sería que una clase abstracta puede contener propiedades privadas & métodos, donde una interfaz no puede, ya que es solo un contrato simple.

El resultado es que una interfaz siempre es "sin engaños aquí, lo que ves es lo que obtienes", mientras que una clase base abstracta puede permitir efectos secundarios.

+0

¿Por qué sería importante para un repositorio o algo por el estilo? La idea de la encapsulación es que hace algo sin que tengas que preocuparte sobre cómo - si hay chanchullos en marcha, entonces tal vez sea por una buena razón y no deberías saberlo o preocuparte (por supuesto, cuando lo necesites). saber, pero eso es una abstracción con goteras) – Ryan

+0

Bueno, interfaces y clases abstractas resuelven un problema similar "Definir un contrato común" - Personalmente prefiero las interfaces, pero solo por la razón especificada anteriormente, y quizás porque las definiciones parecen más nítidas. Puede ser una de esas cosas que es simplemente un gusto personal. –

3

Esta es una pregunta general que se aplica a cualquier jerarquía de clases, no solo a repositorios. Desde un punto de vista OO puro, una interfaz y una clase abstracta pura son lo mismo.

Si su clase es parte de una API pública, la principal ventaja de utilizar una clase abstracta es que puede agregar métodos en el futuro con poco riesgo de romper implementaciones existentes.

A algunas personas también les gusta definir una interfaz como "algo que una clase puede hacer" y una clase base como "qué clase es", y por lo tanto solo usarán interfaces para capacidades periféricas y siempre definirán la función primaria (ej . repositorio) como una clase. No estoy seguro de dónde estoy parado en esto.

Para responder a su pregunta, no creo que haya ninguna ventaja al usar una interfaz cuando define la función primaria de la clase.

1

Eche un vistazo a la implementación del Marco de depósito de Tim McCarthy. < http://dddpds.codeplex.com/>

Se utiliza interfaces como IRepository<T> para la definición de los contratos, sino que también utiliza las clases abstractas como RepositoryBase<T> o su SqlCeRepositoryBase <T> que implementa IRepository<T>. La clase base abstracta es un código para eliminar muchos códigos duplicados. Un repositorio específico de tipo solo tiene que heredarse de la clase base abstracta y necesita agregar el código para su propósito. Los usuarios de la API solo pueden codificar contra la interfaz por contrato.

Así que puede combinar ambos enfoques para aprovechar sus ventajas.

Además, creo que la mayoría de IoC-Frameworks puede manejar clases abstractas.

2

Dado que el patrón se origina en Domain Driven Design, he aquí una respuesta DDD:

El contrato de un repositorio se define generalmente en la capa de dominio. Esto permite que los objetos en las capas de Dominio y Aplicación manipulen abstracciones de Repositorios sin preocuparse por su implementación real y los detalles de almacenamiento subyacentes, en otras palabras, ser ignorante de la persistencia. Además, a menudo queremos que los comportamientos específicos se incluyan en los contratos de algunos repositorios (además de su vainilla Agregar(), GetById(), etc.) por lo que prefiero el formulario ISomeEntityRepository que solo IRepository<SomeEntity> - ya veremos por qué lo necesitan para ser interfaces más tarde.

Las implementaciones concretas de Repositorios, por otro lado, residen en la capa de Infraestructura (o en el módulo Pruebas para repositorios de prueba). Implementan el contrato de repositorio anterior pero también tienen su propio rango de características específicas de persistencia. Por ejemplo, si está usando NHibernate para persistir sus entidades, tener una superclase para todos los repositorios de NHibernate con la sesión de NHibernate y otras instalaciones genéricas relacionadas con NHibernate podría ser útil.

Dado que no puede heredar varias clases, una de estas dos cosas que hereda su Repositorio concreto final tiene que ser una interfaz.

Es más lógico para el contrato capa de dominio para que sea una interfaz (ISomeEntityRepository) ya que es una abstracción puramente enunciativo y no debe hacer ninguna suposición sobre lo que será utilizado mecanismo de persistencia subyacente - es decir, no debe poner en práctica nada .

La persistencia de una específica puede ser una clase abstracta (NHibernateRepository o NHibernateRepository<T> en la capa de la infraestructura) que le permite centralizar hay algunos comportamientos que son comunes a toda la gama de repositorios persistente específicos de la tienda que existirán.

Este resultado en algo así como:

public class SomeEntityRepository : NHibernateRepository<SomeEntity>, ISomeEntityRepository 
{ 
    //... 
} 
Cuestiones relacionadas