Any tour of SOLID should start with the Open/Closed principle. The Open/Closed principle is actually different from the others. All of the others are development practices. OCP is a philosophical goal. Here’s the classic statement:
Objects should be open for extension but closed for modification.
A great phrase, but meaningless unless you’ve had it explained to you. Now, the SOLID principles are all about improving project velocity. What the Open/Closed Principle actually says is: you don’t want to be editing code to change its behaviour. You should be changing a class to fix a bug, not to make it do something else. Now, when this was originally formulated, TDD was in its infancy and changing code was regarded as being inherently fragile. We’ve mitigated that with TDD, but actually it turns out that the same principles enable testing: you shouldn’t have a separate code path for testing.
Let’s take a look at some examples of code that fail the Open / Closed test.
public void PrintLines(string[] lines) { foreach (string line in lines) { Console.WriteLine(line); } }
Okay, let’s think about how we’ve violated the open closed principle. First off, we’ve got a great big ugly static method. I’ve talked a fair bit about these already. Let’s talk about possible scenarios that could come up:
- What happened if you wanted to write to a file? You’d have to change the code.
- What happened if you wanted to disable writing to anywhere? Because the function isn’t virtual, you’d have to change the code.
- What happened if the lines were streaming from a database? Passing them in as an array isn’t ideal so you’d have to change the code.
Let’s look at another example:
public void PrintLines(string[] lines) { using (var writer = new StreamWriter(@"c:x.txt")) { foreach (string line in lines) { writer.WriteLine(line); } } }
Now, obviously many of the objections to the last code are valid again, but this one’s got some more to worry about:
- You can’t change the filename.
- Even assuming you only ever wanted to write to a file, you can’t choose to buffer the file.
Finally, consider this code:
public void PrintLines(ILineProvider lineProvider) { using (var writer = new StreamWriter(@"c:x.txt")) { foreach (string line in lineProvider.Lines) { writer.WriteLine(line); } } }
This has an interface in it, so it must be better, right? Sadly, it isn’t. This code is actually less flexible than the previous example. Now you’ve got to implement ILineProvider just to use it, not just any old array of strings. This is what is known as the Law of Demeter. The Law of Demeter isn’t explicitly mentioned in the SOLID principles, but it should be. Maybe it could be SOLIDD…
Danger Points
Just these two examples have given us some talking points that highlight points at which you’re likely to violate the Open/Closed principle:
- Static Methods
- Non-virtual methods
- Creating objects
- Too-specific variable declarations
- Hard-coded values
- Using the wrong object (the Law of Demeter)
If there’s a summary to all of this, it is this: be careful what you depend upon. Next, I’ll talk about how we actually go about achieving this.