“Closed for modification.” Extending the behavior of a module does not
result in changes to the source or binary code of the module. The
binary executable version of the module, whether in a linkable
library, a DLL, or a Java .jar, remains untouched.Hall, Gary McLean (2014-10-10). Adaptive Code via C#: Agile coding
with design patterns and SOLID principles (Developer Reference) (p.
208). Pearson Education. Kindle Edition.
In Adaptive Code via C#, the author quotes Robert C. Martin’s explanation for Closed for Modification. How should we understand “The binary executable version of the module, whether in a linkable library, a DLL, or a Java .jar, remains untouched.”
We can use staircase patterns for dependency tree in which interfaces and implementations are in different assemblies, and we can extend implementations in different assemblies, but not sure how reasonable this is. I don’t think copying over the rest of the behavior to the new assembly makes sense. Can someone please visualize this explanation with an example.
2
Can someone please visualize this explanation with an example.
Think of the strategy pattern, dependency injection etc. A simple example would be a logger which uses an interface for the actual output.
Once you defined the “business logic” of checking the correct loglevel etc, this class would live – once it is bug free – forever untouched. And since you abstracted the output-behaviour away, you could inject different implementations of output:
- output to a file
- output to an eventlog
- logging to a database
- logging in JSON-format
- logging in XML
- loging in BSON
The actual logger is closed for modification; it doesn’t need to be modified.
New behaviour could be implemented via dependency injection.
I don’t have access to the larger context of your quote, but I believe the discussion of executables is mostly rhetorical. The idea behind the open-closed principle isn’t that you necessarily would create a new dynamic library for every extension to your program, but that your architecture is structured so that you could if you wanted to. In other words, new features should primarily be implemented by creating new files, not by modifying old ones, even modifications that don’t change the source but would require recompiling the object file (for example, because of a change in the size of a dependent object it stores).
From a lone developer point of view, it may seem contrived to extend your own modules or implement your own interfaces in distinct binaries.
However, bear in mind that it is not always the case. You might have distributed teams with a provider/customer relationship and no access to each other’s source code. You might be a consumer of a third party framework, and happy to not have to touch the framework’s code to adapt it to your needs.
Deployability is also a reason. If you can identify a base part of your module’s behavior that will be relatively stable over time and families of peripheral behaviors which will evolve much more often, you can place those in their own executables (or any independently deployable unit). This will allow for a much more manageable deployment process in terms of continuity of service, update package size, potential side effect scope, etc.
I think the easiest visualization is the extension methods on C#. You can add additional behaviour to a class, without needing to change that class. The extension method itself can be set on a different dll. Ruby is another example in which this ability was baked in from the beginning.
public static class Extensions
{
public static bool IsAllCapitals (this string text)
{
return text.All(x => Char.IsUpper(x));
}
}
// Then called
string text = "TEXT";
text.IsAllCapitals();
Although the main idea on the SOLID principles, I believe, is the use of inheritance, which can add additional behaviour to the class, without having to do the copy/paste that you are talking about. On this case, if you override any method of a class you should still be calling the base implementation. The child class can/should be in a separate dll. You can, of course, create additional public methods or implement other interfaces.
public interface IA
{
void DoSomething();
}
public class A : IA
{
public virtual void DoSomething()
{
Console.WriteLine("In A");
}
}
public class B : A
{
public override void DoSomething()
{
base.DoSomething();
Console.WriteLine("In B");
}
}
Using DI then you can select at runtime which version you want to use, the original, or the extended one.
2