Search code examples
c#design-patternsinheritanceliskov-substitution-principle

Interface inheritance - How to not break Liskov's Substitution Principle and the Single Responsibility Pattern?


I have a generic repository pattern, and I'm now seeing that I need a custom method for one specific implementation of this pattern, let's call the implementation CustomerRepository and the method GetNextAvailableCustomerNumber. I have a few ideas but they're not conforming to the SOLID principles of object-oriented design.

I first considered to make a custom repository pattern (ICustomerRepository) for that implementation, but that is not really very feasible. Experience tells me that there has to be some other way which I have not yet considered or even know about at the present time. Besides, I don't think that inventing a new repository interface for every bump in the road should be done so lightly.

I then considered making ICustomerRepository inherit IRepository<Customer> and just add the method signature for GetNextAvailableCustomer, but that goes very much against Liskov's Substitution Principle and I believe it also goes ever so slightly against the Single Responsibility Pattern. I would still be able to implement a customer repository based on IRepository, even though I'd only want to use ICustomerRepository. I would end up with two alternatives and it would no longer be obvious which interface the client should be implementing. I would in this case wish for it only to be possible to implement ICustomerRepository, and not IRepository<Customer>

What would be the proper way to go about this? Is interface inheritance really the way to go, or is there any other preferred method of approach which ideally would conform to LSP?

This is my generic repository interface:

public interface IRepository<T>
    where T : IEntity
{
    T GetById(int id);

    IList<T> GetAll();

    IEnumerable<T> Query(Func<T, bool> filter);

    int Add(T entity);

    void Remove(T entity);

    void Update(T entity);
}

Solution

  • You are actually not breaking Liskov's Substitution Principle. Liskov's says

    objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program

    In your case you can. With your interpretation of Liskov almost no inheritance and extensions of classes will be allowed.

    I think that a ICustomerRepository that "inherents" from IRepository would be just fine. I can still replace ICustomerRepository everywhere where I would use IRepostory (given ICustomerRepository:IRepostory)

    Liskov guards against unexpected behavior of subclasses. The most used (although not necessarily the beste) example seems to be the example where a square inherets from a rectangle. Here we have a SetWidth method that is overridden by Square, but Square also sets the height since it is a square. The original methods definition is therefore changed in the subclass and therefore violates the principle.