In our C# MVC application we have a lot of interfaces that map 1 to 1 with the objects that implement them. ie: basically, for each object created, an "extract interface" operation has been performed.
The interfaces are used by Moq to generate mock objects for our unit tests. But that's the one and only time the interfaces are re-used.
No concrete objects in our system implement multiple interfaces.
Can anyone tell me if this is going to cause problems down the road? And if so, what would they be?
I was thinking, re our app that there is a lot of duplication, for example in these 2 interfaces (Edit: in our SERVICES layer) the only thing that differs is the method name and the type of parameter they take, but semantically they do the same thing with the repositories they send messages to:
interface ICustomer
{
void AddCustomer(Customer toAdd);
void UpdateCustomer(Customer toUpdate);
Customer GetById(int customerId);
}
interface IEmployee
{
void AddEmployee(Employee toBeAdded);
void UpdateEmployee(Employee toUpdate);
Employee GetById(int employeeId);
}
and that's where I think the reused abstraction principle would come in, ie to transform the code to something like:
public interface IEmployee: IAdd<Employee>, IUpdate<Employee>, IFinder<Employee>
This isn't about the repository pattern - this is about interfaces in any layer that look like they share semantically identical behaviours. Is it worth deriving common interfaces for these operations and making "sub-interfaces" inherit from them?
At least it would keep the signatures of the methods consistent. But what other benefits would this give me? (Liskov substitution Principle aside)
Right now, the names of the methods and the return types are all over the place.
I read Mark Seemann's blog about the Reused abstractions Principle but I didn't understand it, to be frank. Maybe I'm just stupid :) I also read Fowler's definition of Header Interfaces.
The abstraction principle states that any complicated capability has its own abstract realization patterns. Each abstract realization pattern defines a way to decompose a complicated capability into a set of simpler capabilities.
In software engineering and programming language theory, the abstraction principle (or the principle of abstraction) is a basic dictum that aims to reduce duplication of information in a program (usually with emphasis on code duplication) whenever practical by making use of abstractions provided by the programming ...
Given this:
interface ICustomer{
void AddCustomer(Customer toAdd);
void UpdateCustomer(Customer toUpdate);
Customer GetById(int customerId);
}
interface IEmployee
{
void AddEmployee(Employee toBeAdded);
void UpdateEmployee(Employee toUpdate);
Employee GetById(int employeeId);
}
I'd probably start by redesigning it like this:
interface IRepository<T>
{
void Add(T toAdd);
void Update(T toUpdate);
T GetById(int id);
}
However, this may still very well be violating the Interface Segregation Principle, not to mention that since it also violates the Command-Query Responsibility Segregation pattern (not the architecture) it also can't be made neither co- nor contravariant.
Thus, my next step might be to split these up into Role Interfaces:
interface IAdder<T>
{
void Add(T toAdd);
}
interface IUpdater<T>
{
void Update(T toAdd);
}
interface IReader<T>
{
T GetById(int id);
}
Furthermore, you might notice that IAdder<T>
and IUpdater<T>
are structurally identical (they are only semantically different), so why not make them one:
interface ICommand<T>
{
void Execute(T item);
}
To stay consistent the, you could rename IReader<T>
as well:
interface IQuery<T>
{
T GetById(int id);
}
Essentially, you can reduce everything to these two interfaces, but for some people this may be too abstract, and carry too little semantic information.
However, I don't think it's possible to provide a better answer, because the premise is flawed. The initial question is how the interface should be designed, but the client is nowhere in sight. As APPP ch. 11 teaches us, "clients [...] own the abstract interfaces" - in other words, the client defines the interface, based on what it needs. Interfaces shouldn't be extracted from concrete classes.
Further study materials on this subject:
All of that can be united using the Repository pattern
...
public interface IRepository<TEntity> where TEntity : IEntity
{
T FindById(string Id);
T Create(T t);
bool Update(T t);
bool Delete(T t);
}
public interface IEntity
{
string Id { get; set; }
}
No concrete objects in our system implement multiple interfaces.
Can anyone tell me if this is going to cause problems down the road? And if so, what would they be?
Yes, it will cause problems if it hasn't started to do so already.
You'll end up with a bunch of interfaces which adds nothing to your solution, drains a large proportion of your time in maintaining and creating them. As your code base increases in size, you'll find that not everything is as cohesive as you once thought
Remember that interfaces are just a tool, a tool to implement a level of abstraction. Whereas abstraction is a concept, a pattern, a prototype that a number of separate entities share.
You've summed this,
This isn't about the repository pattern - this is about interfaces in any layer that look like they share semantically identical behaviours. Is it worth deriving common interfaces for these operations and making "sub-interfaces" inherit from them?
This isn't about interfaces
, this is about abstractions
, the Repository pattern
demonstrates how you can abstract away behaviour that is tailored to a particular object.
The example I've given above doesn't have any methods named AddEmployee
or UpdateEmployee
... such methods are just shallow interfaces, not abstractions.
The concept of the Repository pattern
is apparent in that it defines a set of behaviours which is implemented by a number of different classes, each tailored for a particular entity.
Considering that a Repository is implemented for each entity (UserRepository, BlogRepository, etc.) and considering that each repository must support a core set of functionality (basic CRUD operations), we can take that core set of functionality and define it in an interface, and then implement that very interface in each Repository.
Now we can take what we've learned from the Repository pattern, and apply it to other parts of our application, whereby we define a core set of behaviours that is shared by a number of objects in a new interface, and then deriving from that interface.
public interface IVehicleOperator<TVehicle> where TVehicle : IVehicle
{
void Accelerate();
void Brake();
}
In doing so we no longer have 1:1 mappings, but instead an actual abstraction.
While we're on the topic, it may be worth reviewing the decorator pattern
as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With