Open/Closed Principle (OCP)
This is part of my SOLID Software Principles in Practice post.
OCP states that classes/models should be open for extension, but closed for modification.
Now, many people interpret this as being about locking down your class by making sure that the correct access modifiers are used for the class along with its methods and/or member variables. Well, that’s not the case.
OCP is all about programming to the superclass/interface and ensuring that references to other classes are never to concrete implementations. Programming to the superclass/interface leads to code being closed for modification, as the client is only ever referencing abstractions, and open for extension by means of adding more implementations of those abstractions.
Let’s look at an example code snippet to clarify what I just said. The following code breaks the OCP principle:
public final class Invaders {
private final Collection<Invader> invaders = ...;
private final SimpleInvaderMover simpleInvaderMover = new SimpleInvaderMover();
private final OpenGlInvaderRenderer openGlInvaderRenderer = new OpenGlInvaderRenderer();
...
public void mainLoop() {
for (Invader invader : invaders) {
simpleInvaderMover.move(invader);
openGlInvaderRenderer.render(invader);
}
}
}
In the code snippet above, both SimpleInvaderMover and OpenGlInvaderRenderer are concrete classes. Let’s assume that neither of these classes can be changed. Now, if we want to change how Invaders move, or what type of surface they are rendered to, we need to modify the Invaders class directly - i.e. it is open for modification, which is bad!
What we should do instead, is modify the class structures so that we can program to the superclass/interface:
We can then modify the Invaders class like so:
public final class Invaders {
private Collection<Invader> invaders = ...;
// See DIP for how these are instantiated
private InvaderMover invaderMover = ...;
private InvaderRenderer invaderRenderer = ...;
...
public void mainLoop() {
for (Invader invader : invaders) {
invaderMover.move(invader);
invaderRenderer.render(invader);
}
}
}
The Invaders class now knows nothing about concrete implementations. Should we now want to move Invaders in a different way, or, render them to a different surface, we no longer need to modify the Invaders class directly - i.e. it is closed for modification. Instead, we have opened it for extension by means of being able to inject the concrete implementations at runtime. More on injections later.
Note that I have intentionally omitted the instantiation details for invaderMover and invaderRenderer, as this is covered below as part of the Dependency Inversion Principle (DIP). The two principles are very closely tied.