Quote of the Day

more Quotes

Categories

Buy me a coffee

Notes on the Interface Segregation Pattern

Published August 29, 2021 in Architecture , Design Patterns - 0 Comments

The Interface Segregation Pattern (ISP) is one of the principle in SOLID. As a recap, SOLID is an acronym which stands for the five software design principles:

  • The Single Responsibility Principle
  • The Open Closed Principle
  • The Liskov Substitution Principle
  • The Interface Segregation Principle
  • The Dependency Inversion Principle

ISP states that a module should not depend on other modules that it does not need. This pattern can help make your components more cohesive and less coupling. How? Your modules are more cohesive if each of the modules don’t depend on other modules that it does not need.

ISP works in tandem with other patterns such as the Single Responsibility Principle (SRP) and the Open Closed Principle (OCP) to make your application more cohesive and less coupling. SRP states that a module should have only one reason to change. When a module adheres to SRP, it’s less likely to depend on other modules that do not support the change. However, when the module violates the SRP, chances are the module also contain different functionalities or groups of functionalities that do not depend on one another. In this case, you should break the module into multiple ones, each is responsible for one reason to change. Similarly, if a module adheres to OCP, you can add new modules without having to modify the existing one to add new or extend existing functionalities. As such, it’s less likely you are going to have disparate functionalities or unnecessary dependencies in your module.

Even when a module adheres to SRP and/or OCP, it may still violates ISP. In the book Clean Architecture (Martin, 2017), the author gives an example of the ISP and language. For instance, sometimes you may not have control over which dependencies your module depends on. As an example, suppose your module depends on a library L. In turns, L depends on database D. So, your module may have to depend on D even though you don’t need any features in D. This may be something you can’t control if you have to depend on L. However, in case when you have control, which is when you design or implement your modules, you can and should take care to minimize extra dependencies your module has.

When I refactor my codes, and not sure which steps to take to move the codes in a better direction, I often think of ISP and other design principles. For example, I look to see if certain variables in a module are referenced by some methods but not the others. If so, it’s an indication that the module does not adhere to ISP. Often, it makes sense for me to extract those variables and methods into another class or interface. On doing so, I may have to refactor other clients of those methods to reference the new class. At the end of the refactor, I often end up with modules that are more compliant to the ISP. On a side note, it’s advisable to do refactoring on an ad hoc basis and with tests. For instance, when adding a new feature or updating an existing one, if the existing design is cumbersome or make the changes difficult, it’s a good idea to refactor. It’s prudent to have tests in place before refactoring if possible, and tests for any new behaviors. If you want to learn more on refactoring techniques, checkout these two books: Refactoring (Fowler, 2018) and Working Effectively With Legacy Code (Feathers, 2004).

In several projects I have worked on, I have a service layer, and all it does is delegating to the corresponding module in the repository layer. Furthermore, the service layer often contains various methods for accomplishing different things, which may or may not relate to one another. For instance, I may have a service for the Album entity which corresponds to the Album table in the database. In this service, I have different methods for the CRUD operations on the Album. Multiple controllers reference the Album service. For example, in a Contributor controller, I call the Album service to retrieve all the albums which the contributor is a part of. In the Song contributor, I call a method in the Album service to retrieve the albums of which the song is a part of. The Contributor controller uses one method in the Album, but it does not use the other methods which the Song controller references . By grouping all of the methods under the Album service, my codes do not comply with the ISP. Furthermore, sometimes I have a hard time deciding which modules I should place specific codes. For example, if I have a service which retrieves the albums that contain a specific contributor, should I place that service in the Album or the Contributor service class? Nowadays, my preference is to use the Command Query Responsibility Segregation (CQRS) and mediator instead of generic services. Using CQRS and mediator, it’s more clear where I should place the logic. I just need to encapsulate the operation into a request and a handle class. Those two class together encapsulates the operation and nothing more, making my codes more compliant to the ISP and other design principles.

References

Clean Architecture: A Craftsman’s Guide to Software Structure and Design (Robert C. Martin Series)

Working Effectively with Legacy Code

Refactoring: Improving the Design of Existing Code (2nd Edition) (Addison-Wesley Signature Series (Fowler))

CQRS pattern using MediatR in .NET 5

No comments yet