Changing the current implementation violates the open-closed principle of object oriented programming and can surely lead to breaking code (even if inadvertently).
The safest thing would be if could write some new code for this functionality and somehow invoke it with no changes to the existing code.
But how to do this?The way I like to introduce new functionality is by writing multiple implementations of the same interface on the called side of the seam.
This introduces boundaries orthogonal to the earlier boundary.
However, these are very different from what we were talking about before.
These act more like isolation chambers or silos — there is no interaction across these, stuff on one side isn’t even aware that there are others like itself on the other side, and we can keep adding more of these, thereby continuously widening the variety of functionality offered.
So to add new functionality, we just add one more implementation and stick it between the existing ones.
There are our seams of extension.
However, now the caller has a problem.
How should it invoke one or more of the many implementations available without changing a lot of code?Orthogonal seams provide room for extension without interfering with existing codeEnter Resource Locator (aka Service Locator).
We introduce a resource locator to which all implementations register itself, and we expose this to the caller instead of having him bind directly to the interface (via dependency injection etc).
The resource locator now allows the caller to get access to “named” instances of the implementation (names being unique) and hence putting the caller in control of what code to invoke.
The locator thus acts a bridge across the seam, allowing the caller the choice of picking the appropriate implementation out of a multitude of them.
Once the name instance is return by the locator, the caller can invoke it in a similar manner as before, and we should not see any behavioural difference.
Resource Locator provides access to multiple implementations in a dynamic scenarioThe overall effect this has on the code structure is that we can continue to add more and more functionality to our code without having to modify existing code.
It also keeps intact the contract between caller and callee, so that the testability across the seam is preserved.
In fact, mocking of code for testing purposes becomes even easier since now there is a central access point (the resource locator) which can be manipulated to give whatever implementation is needed for testing.
There is a change in the nature of the coupling as well — the caller was previously unaware of the implementation on the other side of the seam, but now it has to ask for some specific implementation.
it is directly aware that there are multiple available flavours to choose from.
This awareness is not always desirable, hence this kind of design should only be used for scenarios where dynamic conditions on the calling side dictate what implementation to use, and hence we cannot bind the implementation to the caller at compile time.
It is also useful when building code superstructures that require multiple implementation of the same interface to be chained by some framework code (request filters in web servers are a good example of this).
The resource locator+seams combinations allows us to write more and more implementation and adding them to the chain by adding their names to some configuration that the framework can read and process.
.. More details