In my last post we went over LSP ( Liskov Substitution principle ) , how it helps achieve Subtype Polymorphism. We learnt how to think in terms of client programs and how client’s call to programs can influence the quality and design of applications. The SRP and OCP are foundational principles on which LSP and the next one we discuss today , ISP manifest themselves. It is not enough if Single Responsibility and Open Closed are understood – the rest of the three principles are where you see their applications and extensions. LSP , ISP and DIP all three teach us how to design from a client’s point of view. Uncle Bob calls it as “Clients exerting forces “ .
The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use
ISP helps formulate mechanisms on how interfaces should be segregated so the same interface can be useful to different clients. It’s a very powerful thought that can bring strong results. Single Responsibility talks about writing interfaces that have very cohesive functions . ISP takes it one step further and gives concrete ideas that give rise to reusable interfaces . This is where the relationship and the difference between SRP and ISP exists .
So how does ‘Segregating’ Interfaces help the clients ? Interfaces are meant to represent a certain type of behavior to the client and by virtue of their contractual representation promote pluggability. So when interfaces represent more than one type of behavior , they provide more than what a client needs . This leads to the interface becoming incapable of being used for plug-in purposes. Also they become ‘Fat’ in nature , with unncessary behavious not useful in the client’s context. Basically this is solidifying more on the SRP and OCP and stressing a whole lot more on how interface contracts should represent one type of behavior. Almost all patterns have a basis in SRP , OCP and ISP. Let’s say we are writing a class to represent a Persistence Medium. One of the most popular persistence medium is Database . So is XML . And a lot of applications use simple JSON files to store simple content. Say , we start out by writing a PersistenceMedium class. So a simpe IPersistenceMedium looks like this :
Code is pseudo only , to describe the principle , not a working , compiled example :
// IResult is an imaginary interface interface IPersistenceMedium { string fileName { get; set; } string connectionString { get; set; } void Open(); void IResult ExecuteQuery(string query); void IResult ReadFile(); void Close(); } //So our database class can be written like this: public class Database : IPersistenceMedium { private string _connectionString; public string connectionString { get{ return _connectionString; } set{ _connectionString = value; } } public void Open(){ /* database open connection code */ } public IResult ExecuteQuery( string query ); public void Close(){ /* database close connection code */ } } // The JSONStore can be written like this : public class JSONStore : IPersistenceMedium { private string _fileName; public string fileName { get{ return _fileName; } set{ _fileName = value; } } public void Open(){ /* open a JSON file code */} public IResult ReadFile(); public void Close(){ /* close file code */} }
See what happened above ? The Database class could not use ReadFile() and fileName and JSONStore could not use ExecuteQuery() and connectionString . Although both are variants of a PersistenceMedium behaviour , they are unnecessarily clubbed together from the perspective of Database and JSONStore classes which are the clients of IPersistenceMedium.
A Better way would be :
interface IFileConnection { string fileName { get; set; } void Open(); void Close(); } interface IDatabaseConnection { string connectionString { get ; set; } void Open(); void Close(); } interface IDBOperation { IResult ExcecuteQuery(); } interface IFileOperation { IResult ReadFile(); }
Now this provides interfaces that can be specifically used for Database or JSONStore because we segregated them keeping in mind different clients / client groups. The interfaces which can be used for JSONStore can be used for XML or any other type of file store. Similarly the interfaces that got written for Database can be used for any type of database , SQL , Oracle or NoSQL . The interface is less fat , with interfaces ‘Segregated’ strictly based on different client usages . This is SRP in effect, by using ISP. What are the different ways of using these with the Clients ? One obvious way is multiple inheritance.
public class JSONStore : IFileConnection , IFileOperation { private string _fileName; public string fileName { get{ return _fileName; } set{ _fileName = value; } } public void Open(){ /* open file*/ } public IResult ReadFile(){ /* Read File */} public void Close() { /* Close File */ } } public class Database : IDatabaseConnection , IDatabaseOperation { private string _connectionString; public string connectionString { get{ return _connectionString; } set{ _connectionString = value; } } public void Open() { /* Open Connection */} public IResult ExecuteQuery(){ /* execute query to obtain results */} public void Close(){ /* Close Connection */} }
The above JSONStore could be very well be written as FileStore , in that case different FileStores can be used and IResult representing different result types form different types of files.
There are different ways to implement the above without multiple inheritance. One way is to use something like a Strategy pattern where IFileOperation has concrete classes for JsonFileOperation and XmlFileOperation – because each may have subtle read differences. XML requires special parsing and JSON is more of a string representation.
We have been able to establish in many different ways the value of SOLID – how it leads to good design practices and understanding of patterns.
I will conclude without diving into Dependency Inversion – there are fantastic blogs including Uncle Bob’s report. Dependency Inversion distinguishes itself by establishing how layers should interact without depending on each other. Again a powerful concept that has led to great programming practices including Test Driven Development and several patterns that facilitate the principle. SOLID is definitely worth your time !
One thought on “SOLID conclusions with ISP and DIP”