Home > Enterprise >  Inherit an abstract class and an interface same time
Inherit an abstract class and an interface same time

Time:09-27

In this course project, the teacher created an abstract base class (EfEntityRepositoryBase) for data access, a concrete class for each entity (ProductDal) that inherits abstract base class and implements an interface (IEntityRepository). ProductDal also has its interface (IProductDal) which also implements IEntityRepository.

What is the use case for doing it? I can't understand the point of IProductDal implementing IEntityRepository, since ProductDal already inherits the abstract base class that implements the same interface. So if any function updates in IEntityRepository, should be no problem. If someone can explain so would be great. Below abstract class and interfaces code.

public class ProductDal : EfEntityRepositoryBase<Product>, IProductDal{ }

public interface IEntityRepository<T>
{
    void Add(T entity);
    void Delete(T entity);
    void Update(T entity);
    List<T> GetAll(Expression<Func<T, bool>> expression = null);
    T GetById(Expression<Func<T, bool>> expression);
}

public interface IProductDal: IEntityRepository<Product>
{
}

public class EfEntityRepositoryBase<TEntity> : IEntityRepository<TEntity> where TEntity : class, IEntity, new()
{
    public void Add(TEntity entity)
    {
        using (BookStoreTrackerDBContext context = new BookStoreTrackerDBContext())
        {
            var addedEntity = context.Entry(entity);
            addedEntity.State = EntityState.Added;
            context.SaveChanges();
        }
    }
}

CodePudding user response:

I think it is easy to understand that you feel, when looking at your provided example, tempted to call out the IProductDal interface as being redundant. In fact, it doesn't add any extra members to the type ProductDal because the interface IProductDal and the generic class EfEntityRepositoryBase are defined with the same generic parameter type Product. Since those teaching examples are not set in context of real application code, their real intentions or ideas behind them are not easy to understand.


As a side note, you should know that in case the class EfEntityRepositoryBase<TEntity> would be defined with a different generic parameter type than Product e.g., int, ProductDal would have two implementations/member overloads of the IEntityRepository<T> interface. For example:

public class ProductDal : EfEntityRepositoryBase<int>, IProductDal
{ 
  // Implementation of IProductDal. The EfEntityRepositoryBase type provides another 'int' overload
  public void Add(Product entity) {}
}

void Main()
{
  var productDal = new ProductDal();
  
  // Implementation of IEntityRepository<int> provided by class EfEntityRepositoryBase<int>
  productDal.Add(6);

  // Implementation of 'IProductDal' (provided by class 'ProductDal')  
  productDal.Add(new Product());
}

You can see that your provided example shows a special case where the EfEntityRepositoryBase<TEntity> already provides the implementation for the IEntityRepository<Product> and the IProductDal interfaces.


Back to your example: if you make use of type casting, you will find another use case for having the alledgedly redundant type definitions:

Given is your ProductDal type with the following class signature

public class ProductDal : EfEntityRepositoryBase<int>, IProductDal

You have now multiple types available to access the implementation of IEntityRepository<Product>

void Main()
{
  // Create an instance of ProducDal
  ProductDal productDal = new ProductDal();

  /* Use the instance of ProductDal with multiple overloads 
     to show implicit type casting */
  UseProductDal(productDal);
  UseIProductDal(productDal);
  UseIEntityRepository(productDal);
  UseEntityRepository(productDal);
}

void UseProductDal(ProductDal productDal)
{
  // Instantiate the argument
  var product = new Product(); 
  productDal.Add(product);
}

void UseIProductDal(IProductDal productDal)
{
  // Instantiate the argument
  var product = new Product(); 
  productDal.Add(product);
}

void UseIEntityRepository(IEntityRepository<Product> productDal)
{
  // Instantiate the argument
  var product = new Product(); 

  productDal.Add(product);
}

void UseEntityRepositoryBase(EntityRepositoryBase<Product> productDal)
{
  // Instantiate the argument
  var product = new Product(); 
  productDal.Add(product);
}

This shows how to make use of the implicit type casting and also how to use interfaces.
You now see that although EntityRepositoryBase<Product> already implements IEntityRepository<Product>, still having ProductDal additionally implement the IProductDal interface makes perfect sense in order to enable ProductDal to be used where only the IProductDal interface is known.


You can make use of interface casting to hide members. For example if you add exclusive members to each interface then this members are only accessible when casting the implementor to the corresponding interface:

public interface IEntityRepository<T>
{
  void Add(T entity);  
}

public interface IProductDal: IEntityRepository<Product>
{
  // Exclusive member. Will be only visible when accessed through this interface. 
  int GetProductCount();
}

Given is your ProductDal type with the following class signature

public class ProductDal : IEfEntityRepository<int>, IProductDal

void Main()
{
  // Create an instance of ProducDal
  ProductDal productDal = new ProductDal();

  /* Use the instance of ProductDal with multiple overloads 
     to show implicit type casting */
  UseProductDal(productDal);
  UseIProductDal(productDal);
  UseIEntityRepository(productDal);
  UseEntityRepository(productDal);
}

// All implemented interfaces are visible since there is no casting involved.
// All members are referenced via the implementor type ProductDal.
void UseProductDal(ProductDal productDal)
{
  // Instantiate the argument
  var product = new Product(); 
  
  productDal.Add(product);
  int productCount = productDal.getProductCount();
}

// Only 'IProductDal' is visible since there is an implicit cast to an interface type involved
void UseIProductDal(IProductDal productDal)
{
  // Instantiate the argument
  var product = new Product(); 

  // 'Add()' is provided by 'IEntityRepository<T>', 
  // which is implemented by 'IProductDal' and therefore "visible"
  productDal.Add(product); 

  // 'GetProductCount()' is provided by 'IProductDal'
  int productCount = productDal.GetProductCount();
}

// Only 'IEntityRepository<T>' is visible since there is an implicit cast to the interface type 
void UseIEntityRepository(IEntityRepository<Product> productDal)
{
  // Instantiate the argument
  var product = new Product(); 

  productDal.Add(product);

  // 'GetProductCount()' is only available via the 'IProductDal' interface. 
  // It's not visible here.
  //int productCount = productDal.GetProductCount();
}

CodePudding user response:

At the root of this question is how interfaces are used in dependency injection and mocking / unit testing.

Let's look at what each of these classes and interfaces give you...

ProductDal

This class contains the logic for interacting with Product instances held in your data store.

IProductDal

Where you want to use a ProductDal to interact with Product instances in your data store, declaring it as a IProductDal instead allows your unit tests for code which is dependent on ProductDal to create mock instances of IProductDal which behave however is needed for your unit tests, so that your unit tests for code which uses ProductDal aren't dependent on an actual database. In your production code you can use dependency injection to inject a real ProductDal where an IProductDal is declared.

IEntityRepository

This looks like a fairly generic interface specifying some basic CRUD operations which might be useful for any entity which you might hold in a data store, and it's agnostic of what the type of data store is (e.g. SQL server / MongoDB / Cassandra / imp with a filing cabinet full of notebooks) or what Object Relational Mapper (e.g. Entity Framework / NHibernate) you might be using to access the data store.

EfEntityRepositoryBase

This abstract class contains an implementation of the basic CRUD methods in IEntityRepository which is specific to Entity Framework, to save you repeating those methods in your ProductDal etc classes. You could have another abstract class with implementations of those methods for another ORM (e.g. NHibernate), and if you were to switch from using Entity Framework to using NHibernate then you'd just need to change the inheritance of each of your ProductDal etc classes so that they derive from NHibernateEntityRepository<T> rather than EfEntityRepositoryBase<T>.

But back to the question...

I can't understand the point of IProductDal implementing IEntityRepository, since ProductDal already inherits the abstract base class that implements the same interface

If you're using ProductDal rather than IProductDal wherever you want to interact with Product instances in your data store, then it doesn't really make any difference in your production code, but your unit tests are going to be really difficult to write, and probably slow to run and rather flaky too, because they'll depend on a real data store which is in exactly the right state at the start of each test case.

If instead you're using IProductDal to interact with Product instances in your data store, and using dependency injection to use a ProductDal where a IProductDal is needed by your production code (so that unit tests for that code aren't dependent on a real data store), you'll hit a different problem. Consider this unit test (which uses the Moq mocking framework, but the same issue exists regardless of what mocking framework you use):

[Fact]
public void Test1()
{
    var mockProductDal = new Mock<IProductDal>();
    mockProductDal.Setup(m => m.GetAll(null))
                  .Returns(new List<Product> { new Product { } });
}

In the above test, the compiler only understands m => m.GetAll if IProductDal is derived from EfEntityRepositoryBase (which is where the GetAll method is defined).

Similar problems will also turn up in any of your production code which uses IProductDal - after all, that code is a consumer of IProductDal / ProductDal in the same way that the above unit test is.

In short...

Reference the interface rather than the implementation in your production code. Use dependency injection in your production code to inject the real implementation, and mock the interface in your unit tests to remove dependencies on external resources such as data stores. If you do this then you also need to do apparently strange things like making Interface1 implement Interface2 when the class which implements Interface1 is derived from a class which implements Interface2. But it'll make your unit tests easier to write, and better, and better unit tests can only be a good thing.

  • Related