Interfaces and Replacement: The Liskov Substitution Principle

We were talking about plugins, and how a software as a plugin is a beautiful concept, where we can replace one plugin with another, with the same definition, and our application could work well.

However, the “replacement” part of the story has some challenges I am going to discuss in this post.

TRY IT YOURSELF: You can find this post source code here.

Plugins Series

The Interface Definition

Let’s define our example classes we were working in previous posts:

Service and Repository relationship

We are going to go deep in the implementation, so, let’s see how they would be:

interface Repository{
  User save(User user);
}

We have a User entity:

class User{
  private final String id;
  private final String name;

  public User(String id, String name){
    this.id = id;
    this.name = name;
  }

  public String getId(){
    return id;
  }
}

And finally the most interesting one, our Service class:

class Service{
  private final Repository repository;

  public Service(Repository repository){
    this.repository = repository;
  }
  
  public User save(User user){
    //...
    // Some business rules applied
    //...
    
    User newUser = this.repository.save(user);

    //Doing stuff with user.getId();

    return newUser;
  }
}

Seems fine, isn’t it? well, let’s answer this question first:

User has an ID, who is going to define it?

Well, if we let the code as we have now, it means, Repository interface and its implementations will have this responsibility, or Service class?? it is not explicit anywhere.

NOTE: The Single Responsibility Principle is broken here, you are not sure who must be responsible of that behavior.

Let’s give that responsibility to the Repository interface, is that ok? well, yes, it is, but you should think the following:

  1. We need to let the interface as explicit as possible about his intend. In this case is kind of difficult, so, you should add COMMENTS there, telling that save method is going to generate a new ID (I do not like comments, however, there is not other way for now).
  2. Some databases do not have auto generator for IDs, it means, your Repository implementation needs to deal with it somehow.

Now, what happen to your business rules if the MYSqlRespository implementation doesn’t set the ID? let’s see:

class MYSqlRepository implements Repository{
  // Database access stuff
  //...

  public User save(User user){
    //...
    // More database access stuff
    //...

    return new User(null, /*name*/);
  }
}

Well, the Service class is going to blow up when it tries to use the user ID, and we just break the Liskov Substitution Principle.

TRY IT YOURSELF: You can find this source code here.

The Liskov Substitution Principle

Well, this is Uncle Bob’s definition:

Derived classes must be substitutable for their base classes.

In our example, we cannot replace our Repository interface for MySQLRespository totally free of bugs, due to there were not explicit requirement about generating the user ID.

Move to a Better Solution

Okey, let’s move to a more explicit way to think about the ID:

Giving to the Repository the ID generation

We just create a new method in our Repository interface to create the ID. With this, our business rules will take care of that:

class Service{
  private final Repository repository;

  public Service(Repository repository){
    this.repository = repository;
  }
  
  public User save(User user){
    //...
    // Some business rules applied
    //...
    User userWithId = this.repository.generateId(user);

    User newUser = this.repository.save(userWithId);

    //Doing stuff with user.getId();

    return newUser;
  }
}

Our business rules should take care of when generating the ID, however, Repository is explicitly responsible of it.

Our implementation improves in the following points:

  • We make explicit that Repository interface must know how to generate an ID, it means, any implementation must know too.
  • As the responsibilities are more explicit, we have less ways of breaking the Liskov Substitution Principle.

However, it has some drawbacks too:

  • You should sync your business rules to use the new Repository method, if you forget, well, your logic could fail.
  • Some Repository implementations could have problems trying to implement a generator.
  • And yes, more code to maintain.

TRY IT YOURSELF: You can find this source code here.

Final Thought

To create a software as a plugin, we should guarantee that those plugins can be substituted in a straightforward way.

The Liskov Substitution Principle plays a important role here, if you break it, you won’t be able to replace plugins.

If you liked this post and are interested in hearing more about my journey as a Software Engineer, you can follow me on Twitter and travel together.

Advertisement

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s