Clients should not be forced to depend upon interfaces that they do not use.
Do not force any client to implement an interface which is irrelevant to them
You should prefer many client interfaces rather than one general interface and each interface should have a specific responsibility. Interface Segregation Principle is very much related to the Single Responsibility Principle.
Suppose if you enter a restaurant and you are pure vegetarian. The waiter in that restaurant gave you the menu card which includes vegetarian items, non-vegetarian items, drinks, and sweets. In this case, as a customer, you should have a menu card which includes only vegetarian items, not everything which you don’t eat in your food. Here the menu should be different for different types of customers. The common or general menu card for everyone can be divided into multiple cards instead of just one. Using this principle helps in reducing the side effects and frequency of required changes.
Violating the Interface Segregation Principle
struct Document;
struct IMachine {
virtual void print(Document &doc) = 0;
virtual void fax(Document &doc) = 0;
virtual void scan(Document &doc) = 0;
};
struct MultiFunctionPrinter : IMachine { // OK
void print(Document &doc) override { }
void fax(Document &doc) override { }
void scan(Document &doc) override { }
};
struct Scanner : IMachine { // Not OK
void print(Document &doc) override { /* Blank */ }
void fax(Document &doc) override { /* Blank */ }
void scan(Document &doc) override {
// Do scanning ...
}
};
- As you can see, as far as
MultiFunctionPrinter
was concerned it’s ok to implementprint()
,fax()
&scan()
methods enforced byIMachine
interface. - But what if you only need a
Scanner
orPrinter
, some dev still inheritsIMachine
& leave unnecessary methods blank or throwNotImplemented
exception, either way, you are doing it wrong.
Interface Segregation Principle Example
/* -------------------------------- Interfaces ----------------------------- */
struct IPrinter {
virtual void print(Document &doc) = 0;
};
struct IScanner {
virtual void scan(Document &doc) = 0;
};
/* ------------------------------------------------------------------------ */
struct Printer : IPrinter {
void print(Document &doc) override;
};
struct Scanner : IScanner {
void scan(Document &doc) override;
};
struct IMachine : IPrinter, IScanner { };
struct Machine : IMachine {
IPrinter& m_printer;
IScanner& m_scanner;
Machine(IPrinter &p, IScanner &s) : printer{p}, scanner{s} { }
void print(Document &doc) override { printer.print(doc); }
void scan(Document &doc) override { scanner.scan(doc); }
};
- This gives the flexibility for the clients to combine the abstractions as they may see fit and to provide implementations without unnecessary cargo.
- As explained in the Single Responsibility Principle. You should avoid classes & interfaces with multiple responsibilities. Because they change often and make your software hard to maintain. You should try to split up the interface into multiple interfaces based on role.