I : The Interface Segregation Principle
Make fine-grained interfaces that are client specific.
“Fine-grained interfaces” stands for interfaces with a small amount of methods.
“Client specific” means that interfaces should define methods that make sense from the point of view of the client that uses the interface.
applying the Interface Segregation principle has several advantages.
it will lead to smaller interfaces, which are relevant for a subset of all the clients.
an interface that needs to change less frequently is much preferable, since it will make it easier to maintain backward compatibility.
Violation: Multiple Use Cases
Sometimes the interface of a class contains too many methods because it serves multiple use cases.
Some clients of the object will call a different set of methods than other clients of the same object.
Every existing service container implementation serves as a great example of a class that has different clients, since many service containers are used both as a dependency injection (or inversion of control) container and as a service locator.
The creation logic is all handled by the dependency injection container. This is why such a container is often called an Inversion of Control (IoC) container .
any client that depends on
ServiceContainerInterface
can both fetch previously defined services and define new services.Most clients of
ServiceContainerInterface
only perform one of these tasks. A client eitherconfigures the service container (for example, when the application is bootstrapped)
fetches a service from it (when the application is up and running).
Refactoring: Separate Interfaces and Multiple Inheritance
The difference between clients should be reflected in the interfaces that are available;
For instance, by splitting the interface into a
MutableServiceContainerInterface
and aServiceLocatorInterface
one is for the part of the application that bootstraps the service container by configuring the available services.
other is for client that fetches a service to process a request.
each client can require its own appropriate type of service container.
There would be a single
ServiceContainer
class, which serves both types of clients at the same time by implementing bothMutableServiceContainerInterface
andServiceLocatorInterface
Violation: No Interface, Just a Class
If the class doesn’t implement an interface the best we can do is to use the class itself as the type for the constructor argument.
Even though there is no explicit interface for the class, it still has an implicit interface.
Each method of the class comes with a certain scope (public, protected, or private).
When a client depends on the class, it depends on all the public methods.
None of the methods with a different scope (i.e., protected or private) can be called by a client.
So the public methods combined form the implicit interface
Implicit Changes in the Implicit Interface
We define an EntityManager
class that can be used to persist entities (objects) in a relational database.
The EntityManager class has some public methods — persist()
and flush()
—and one private method that internally makes the UnitOfWork
object available to other methods.
we decide to add a Query
class to ORM package. This Query
class needs the UnitOfWork
object that is used internally by EntityManager
. So we turn its private getUnitOfWork()
method into a public method. That way, the Query class may depend on the EntityManager class and use its getUnitOfWork()
This new public method getUnitOfWork()
will automatically become part of the implicit interface of EntityManager
. From this moment on, all clients of EntityManager implicitly depend on this method too, even though they may only use the persist()
and flush()
methods . Maybe some clients start using the publicly available method getUnitOfWork()
too.
One day we refactor the Query
class and remove its dependency on the EntityManager
class. Since none of the classes need the public getUnitOfWork()
method anymore, make that method private again. Suddenly all the clients that use the previously public getUnitOfWork()
method will break.
Adding methods to the implicit interface of a class is also bound to cause backward compatibility problems.
Refactoring: Add Header and Role Interfaces
Solve this problem by defining an interface for each use case that the class provides.
Martin Fowler calls these different types of interfaces role interfaces and header interfaces,
can determine role interfaces for a class by looking at the different clients that use the class.
Then group the methods that are used together in separate interfaces.
Header interfaces are usually the easiest to define, since all to do is duplicate the public methods of the class, no thought needed
Clients should not be forced to depend on methods they do not use.
\
Continuing the example above ..
Can add a main interface that combines the two interfaces, and a class that implements the main interface.
The interfaces we have defined describe the roles that a class can play: PersistsEntitiesInterface
and HasUnitOfWorkInterface
.
Then there is one interface that combines these roles and together constitutes a thing we know as an entity manager, which “can persist entities” and “has a unit of work”: the EntityManagerInterface
Packages and the Interface Segregation Principle
When adding a small, focused interface to a class in the package, we are free to add more public methods to that class that aren’t part of the published interface.
can even change or remove existing methods that aren’t part of the interface, giving you more freedom to redesign or refactor classes without disturbing its users.
Last updated