zeugor
zeugor

Reputation: 926

interface segregation SOLID principle in Spring services

I keep my service interfaces as lean are possible, normally they are @FunctionalInterface. I try to follow there the interface segregation principle.

Is a good practice that my service implementation implements multiple interfaces if they share most of there dependencies? or should I create a separate implementation for each interface?

// find the T scheduled between the given instants.
interface TaskPeriodFinder<T> {
   List<T> find(Instant initial, Instant end);
}

// schedule a task to be execute in a given time.
interface TaskScheduler<T> {
   void scheduleOn(T task, Instant scheduledOn);
}

// execute the scheduled task when the scheduledOn is reached.
interface TaskExecutor<T> {
   void execute(T task);
}

@Service 
class TaskService implements TaskScheduler<String>, TaskExecutor<>, TaskPeriodFinder<> {
   private final TaskFinder taskFinder;
   private final TaskCreator taskCreator;

   public void scheduleOn(String task, Instant scheduledOn) {
      // TODO
   }
   public void execute(String task) {
      // TODO
   }   
   public List<T> find(Instant initial, Instant end) {
      // TODO
   }
}

In my example; is a good practice that TaskService implement these two interfaces? or should I have a separate class for each interface?

Upvotes: 0

Views: 698

Answers (3)

plalx
plalx

Reputation: 43718

There's no universal rule in how to apply the ISP. It's all about crafting useful abstractions for the clients and finding the right balance. If all the clients are using operations A & B together and these are cohesive then there's no real value gained by segregating the behaviors.

Being overly granular may force clients to depend on multiple "views" of the very same concept to achieve their goal and at the other end of the spectrum, being too coarse forces the clients to depend on much more than they need.

However, if you just end up bundling fine grained interfaces in a larger facade and just rely on the facade then your granular interfaces aren't helpful in any way.

Unless you are building a library where flexibility must be baked in from the very start I'd suggest to let the abstractions appear by themselves based on actual usage rather than going for one method interfaces by default and refactor as you go.

Finding the proper domain abstractions is usually an iterative process and rarely done right from the start. For instance, Java designers failed to recognize early they should have had a read-only interface for lists.

Upvotes: 1

Michael
Michael

Reputation: 44150

If every interface you are creating has exactly one method then you are doing something wrong.

Suppose something wants to find the number of tasks in a period, and add a new task if the number is above or below some arbitrary threshold. With your design, it's impossible. You can either talk to a finder, or an executor, but not a FinderAndExecutor. In such case, you would just need to speak to TaskService directly, completely undermining the point of the interfaces.

Suppose Java's Collection interface were broken up into these interfaces:

interface Clearable {
    void clear();
}

interface Containable<T> {
    boolean contains(T obj);
}

interface Sized {
    int size();
}

interface Removable<T> {
    boolean remove(T obj);
} 

// etc...

Now you need to do something where you clear if size > 100. What type do you use?

public static void clearIfTooBig(??? collection) {
    if (collection.size() > 100) collection.clear();
}

You could possibly do something with type intersection but most people don't know about it and your method signatures would be very clunky.

Upvotes: 2

Mehdi Benmesssaoud
Mehdi Benmesssaoud

Reputation: 534

I think, your way is a good way to keep up with SOLID.

I would suggest that you bundle interfaces together when necessary, in your current example. I suggest that you do the following, group TaskExecutor and TaskSchedular into a new interface called TaskService.

interface TaskService extends TaskSchedular<String>, TaskExecutor<String> {}

And your class can implement TaskService interface. I think this approach favors composition over inheritance.

public class TaskServiceImpl implements TaskService { ... }

Upvotes: 0

Related Questions