Object Oriented Design Basics : Open Closed Principle
To learn about Open Closed Principle
Let's consider the FizzBuzz problem to learn how to apply the Open Closed Principle. FizzBuzz requirements:
- For multiples of 3, print Fizz
- For multiples of 5, print Buzz
- For multiples of 3 and 5, print FizzBuzz
Define classes to implement the above requirements:
class Fizz def value(n) if n % 3 == 0 'Fizz' end end end class Buzz def value(n) if n % 5 == 0 'Buzz' end end end class FizzBuzz def value(n) if n % 15 == 0 'FizzBuzz' end end end
One of the requirement is implicit, because numbers that is not multiple of 3, 5 or 15 should not be transformed. So we need a NoOp class:
class NoFizzBuzz def value(n) n end end
So far, we have the concrete classes that implement the FizzBuzz logic. Notice that we have a uniform interface value(n) that allows clients to program to an interface and not to an implementation. You will see this in action in upcoming steps.
Define FizzBuzzGenerator class that will delegate the FizzBuzz generation to the concrete classes.
class FizzBuzzGenerator def initialize(objects, list) @list = list @objects = objects end def generate result =  @list.each do |num| @objects.each do |l| v = l.value(num) unless v.nil? result << v break end end end result end end
Notice that the dependency is on the message value(num). There is no dependency on the name of a class. So we don't have any references to Fizz, Buzz, FizzBuzz or NoFizzBuzz classes. This class is open for extension and closed for modification. This means we can add more concrete classes such as Fazz that returns multiples of 7 as Fazz, if such a new requirement arises without modifying this class and extend the functionality.
Finally, here is the test run:
objects = [FizzBuzz.new, Fizz.new, Buzz.new, NoFizzBuzz.new] g = FizzBuzzGenerator.new(objects, (1..20).to_a) r = g.generate puts r
The list of concrete classes (objects), needs to change only when new concrete classes are added. Deploying new feature requires additive changes. This means we add new concrete classes and an instance of that object to the objects array. The generator class does not require any modification to the existing code. This results in a flexible and easy to maintain code base. In our solution, notice that we don't have any if-else-elsif statements. If your solution used if-else-elsif then it would require Localized Changes and it would not be Additive Change.
There is a subtle dependency between the FizzBuzzGenerator class and the order of the objects in the test run code. The correct generation of the FizzBuzz sequence depends on the order of objects. This is a quick-and-dirty implementation of Chain of Responsibility pattern. However this example was chosen to illustrate the Open Closed Principle. If the concrete classes have business logic that can be implemented by passing through a chain of handlers independent of the order in which they are executed, this solution would shine. Because, in that case, there would be no dependency on the order of the handlers in the objects array.
In order to understand the concepts explained in this article, implement the feature where you must print Fuzz for multiples of 7. What are the changes required to satisfy the requirement?
In this article, you learned about the Open Closed Principle and how to apply it by working through a FizzBuzz example.
Ace the Technical Interview
- Easily find the gaps in your knowledge
- Get customized lessons based on where you are
- Take consistent action everyday
- Builtin accountability to keep you on track
- You will solve bigger problems over time
- Get the job of your dreams
Take the 30 Day Coding Skills Challenge
Gain confidence to attend the interview