You’ve no doubt heard of the application architecture acronym, “SOLID”. Bob Martin (Uncle Bob) came up with this as a guideline for application architecture. The acronym stands for:
- Single Responsibility Principle (a class should have one and only one reason to change)
- Open Closed Principle (a class should be open for extension but closed for change)
- Liskov’s Substitution Principle (subclasses implementations should do what you expect them to do)
- Interface Segregation Principle (classes shouldn’t have to implement interfaces they don’t need)
- Dependency Inversion Principle (don’t tie implementation to concrete dependencies, but instead code to abstractions (i.e.: interfaces))
When I talk to various folks, I find that a lot of people don’t really get the Open / Closed Principle. To that end, I decided I’d do a quick write up which probably doesn’t do what Uncle Bob was expressing much justice at all, but is hopefully very simple to understand.
In a nutshell, the behavior of a piece of code should be able to be extended without changing it. That sounds very academic to me, doesn’t really make a lot of sense, and doesn’t seem to have much real world value. After all, a solution should only be as complicated as the problem you are trying to solve. Well, another way of looking at this is to say that code which has been tested and put in production shouldn’t be changed. This reduces the chance of introducing new bugs in existing code and thus keeps total cost of ownership down (less bugs = less $$). Ok, that makes a little more sense, but how do you do it?
It’s easier to see this principle in action. Thus, have a look at the code below.
This first code block violates the Open Closed Principle. A dead give away (or commonly code a “code smell”) is the switch case statement. Be on the lookout for those in your code; they are often good indicators of a worthwhile refactoring opportunities.
VIOLATION:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Diagnostics;
6:
7:
8: namespace ConsoleApplication1
9: {
10: /*
11: * This example is a violation the Open Closed Principle (the O in SOLID),
12: * which means that code is "open for extension, but closed for change".
13: *
14: * Consider that the FeedTheAnimals method is far more complicated and has been tested
15: * and moved to production. If you need to add new functionality
16: * for a new animal, such as a cow, you have to change code which has already been tested.
17: *
18: * This isn't truly extensible.
19: */
20:
21: public enum AnimalType
22: {
23: Dog,
24: Cat,
25: Bird
26: }
27:
28:
public
class OpenCloseViolation
29: {
30: public void FeedTheAnimals(AnimalType animalType)
31: {
32: switch (animalType)
33: {
34: case AnimalType.Dog:
35: {
36: Trace.Write("eat meat");
37: break;
38: }
39: case AnimalType.Cat:
40: {
41: Trace.Write("eat fish");
42: break;
43: }
44: case AnimalType.Bird:
45: {
46: Trace.Write("eat worms");
47: break;
48: }
49: default:
50: {
51: Trace.Write("eat grass");
52: break;
53: }
54: }
55: }
56:
57: public void DoWork()
58: {
59: this.FeedTheAnimals(AnimalType.Dog);
60: this.FeedTheAnimals(AnimalType.Cat);
61: this.FeedTheAnimals(AnimalType.Bird);
62: }
63: }
64: }
By contrast, the following code conforms to the Open Closed Principle.
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Diagnostics;
6:
7: namespace ConsoleApplication1
8: {
9: /*
10: * This example conforms to the Open Closed Principle (the O in SOLID),
11: * which means that code is "open for extension, but closed for change".
12: *
13: * In this example, the implementation is left to the subclasses and
14: * FeedTheAnimals never has to change to add new functionality.
15: *
16: * Thus, this is truly extensible.
17: */
18:
19: public class Animal
20: {
21: public virtual void Eat()
22: {
23: Trace.Write("eat grass");
24: }
25: }
26:
27: public class Dog : Animal
28: {
29: public override void Eat()
30: {
31: Trace.Write("eat meat");
32: }
33: }
34:
35: public class Cat : Animal
36: {
37: public override void Eat()
38: {
39: Trace.Write("eat fish");
40: }
41: }
42: public class Bird : Animal
43: {
44: public override void Eat()
45: {
46: Trace.Write("eat worms");
47: }
48: }
49:
50:
public
class OpenCloseConformance
51: {
52: public void FeedTheAnimals(Animal animal)
53: {
54: animal.Eat();
55: }
56:
57: public void DoWork()
58: {
59: Animal dog = new Dog();
60: this.FeedTheAnimals(dog);
61:
62: Animal cat = new Cat();
63: this.FeedTheAnimals(cat);
64:
65: Animal bird = new Bird();
66: this.FeedTheAnimals(bird);
67: }
68: }
69: }
That’s it. It’s a lot easier than it sounds. I hope this helps someone.
Happy coding,
Tom Hundley
Elegant Software Solutions