Object Oriented Design Basics : Open Closed Principle

Objective


To learn about Open Closed Principle

Discussion


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

Steps


Step 1

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

Step 2

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.

Step 3

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.

Step 4

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

Discussion


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.

Exercise


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?

Summary


In this article, you learned about the Open Closed Principle and how to apply it by working through a FizzBuzz example.


Related Articles


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

No spam ever. Unsubscribe anytime.