🤖▶️ Check out the Design Patterns Overview course by Steve @ardalis Smith!Check it out »Hide

DevIQ

Interface Segregation Principle

Interface Segregation Principle

InterfaceSegregation

The Interface Segregation Principle (ISP) states that clients should not be forced to depend on methods that they do not use.  Interfaces should belong to clients, not to libraries or hierarchies. Application developers should favor thin, focused interfaces to "fat" interfaces that offer more functionality than a particular class or method needs.

In many languages, such as C#, interfaces can inherit from multiple other interfaces. Thus, if you need a larger interface in some parts of the application, but not in others, you may be able to compose it from two or more other interfaces. This is also a good approach to keep in mind if you find yourself refactoring a legacy codebase, which already has large interfaces that you can't break. Consider an interface like this one:

1public interface IMembership
2{
3 bool Login(string username, string password);
4 void Logout(string username);
5 Guid Register(string username, string password, string email);
6 void ForgotPassword(string username);
7}

It's easy to imagine such an interface growing completely out of control and having more functionality than any one class would ever require. To keep, say, a login form from having more methods on it than it needs, you could create a login-specific interface, and have the existing interface extend from it:

1public interface ILogin
2{
3 bool Login(string username, string password);
4 void Logout(string username);
5}
6
7public interface IMembership : ILogin
8{
9 Guid Register(string username, string password, string email);
10 void ForgotPassword(string username);
11}

Of course, you can extend this further, as required, perhaps ending up with an original "fat" interface that only exists for legacy reasons, and is totally composed of other interfaces:

1public interface ILogin
2{
3 bool Login(string username, string password);
4 void Logout(string username);
5}
6public interface IRegister
7{
8 Guid Register(string username, string password, string email);
9}
10public interface IForgotPassword
11{
12 void ForgotPassword(string username);
13}
14
15public interface IMembership : ILogin, IRegister, IForgotPassword
16{
17}

Ideally, your thin interfaces should be cohesive, meaning they have groups of operations that logically belong together. This will prevent you from ending up with one-interface-per-method most of the time in real-world systems (as opposed to the above trivial example).

Another benefit of smaller interfaces is that they are easier to implement fully, and thus less likely to break the Liskov Substitution Principle by being only partially implemented. They also provide greater flexibility in how you implement functionality, since parts of a larger interface can be implemented in different ways. Consider the Repository pattern, which usually includes methods for reading as well as writing. A common performance pattern for database reads it so add a caching layer, but this generally only makes sense for read operations. Likewise, scalability can often be improved by queuing commands (like write operations) rather than executing them immediately, but you wouldn't queue queries. Thus, having an IRepository interface composed of an IReadOnlyRepository and an IWriteRepository would allow base implementations that go against a data store and separate implementations that employ caching and queuing, as well.

See Also

References

Edit this page on GitHub

On this page

Sponsored by NimblePros
Sponsored