This post is part of a series of reviews on the book Head First: Object Oriented Analysis and Design. Check out the Introduction post for a full table of contents along with some initial notes.

Last chapter we had a working software and happy customers, until they changed their mind and are now wanting something different.

The third chapter of our beloved book is all about the only existing constant in the world of software analysis and design: change. By using the requirements and use cases from the last chapter, we’ll see how they can help us adapt our software to new situations.

Our dear customers complain that they don’t always hear their dog Fido bark or leaving the control somewhere else, so they now want their dog door to also open if Fido barks at it. No problem, we can handle it. First we should adjust our use case to the new alternate needs:

  1. Fido (the dog) barks to be let out.
  2. Fido is heard barking by his owners.
    1. Fido is heard by the bark recognizer.
  3. The remote control button is pressed.
    1. The bark recognizer sends a request to the door to open.
  4. The dog door opens.
  5. Fido goes outside.
  6. Fido does his business.
    1. The door shuts automatically.
    2. Fido barks to be let back inside.
    3. Fido is heard barking again.
      1. Fido is heard by the bark recognizer again.
    4. The remote control is pressed again.
      1. The bark recognizer sends a request to the door to open.
    5. The dog door opens again.
  7. Fido goes back inside.
  8. The door shuts automatically.

That looks awfully too confusing and it’s not making a lot of sense either, I mean, we’re still taking into consideration that Fido will always be heard by his owners, and that only sometimes the bark recognizer will hear it, which is not the desired path. The owners want to avoid as much as possible pressing the remote control (having to press only case it gets stuck or something alike), so recognizing a bark should be part of a main path, not the alternate one. Let’s rebuild the use case to what makes more sense to us hence we’re the ones using it anyway.

Main Path Alternate Paths
1. Fido barks to be let out.
2. The bark recognizer “hears” a bark. 2.1. Fido’s owners hear it barking.
3. The bark recognizer sends a request to the door to open. 3.1. Fido’s owners press the button on the remote.
4. The dog door opens.
5. Fido goes outside.
6. Fido does his business.
6.1. The door shuts automatically.
6.2. Fido barks to be let back inside
6.3. The bark recognizer “hears” a bark (again). 6.3.1. Fido’s owners hear it barking (again).
6.4. The bark recognizer sends a request to the door to open. 6.4.1. Fido’s owners press the button on the remote.
6.5. The dog door opens (again).
7. Fido goes back inside.
8. The door shuts automatically.

This use case can lead to various scenarios, which are a particular path through the use case that goes from start to finish. Although we may have various scenarios, we have a single goal: get the dog out and back in again. But before we start coding we need to fill in the new requirements, which should be altered every time we change our use cases, so that we can know for sure the new goals we need to focus on:

  1. The dog door must be at least 12″ (enough for Fido to get through) tall.
  2. A button on the remote control opens the dog door if the door is closed, and closes the dog door if the door is open
  3. Once the dog door has opened, it should close automatically if the door isn’t already closed.
  4. A bark recognizer must be able to tell when a dog is barking.
  5. The bark recognizer must open the dog door when it hears barking.

So we know that we have to create a bark recognizer, which basically means another class on the system we inherited from chapter 2:

class BarkRecognizer

  def initialize(door)
    @door = door
  end

  def recognize(bark)
    puts "Bark recognizer: Heard a '#{bark}'"
    @door.open
  end
  
end

Really hard, huh? But we’ve got a little problem, that code isn’t actually closing the door automatically! Oh, right, we could just add the same code we put in the Remote class, but would that be cool? No. The idea is to avoid duplicate code. Instead of making other things close the door automatically, why don’t we simply make the door itself close automatically? All we have to do is alter the open method in the DogDoor class:

class DogDoor
  attr_reader :is_open

  def initialize
    @is_open = false
  end

  def open
    puts "The dog door opens.\n\n"
    @is_open = true
    Thread.new do
        sleep 2
        self.close
      end
  end

  def close
    puts "The dog door closes.\n\n"
    @is_open = false
  end
  
end

So now that the open method is returning a thread, every method that has as its last call the DogDoor#open method will return a thread as well, which means that we can assign a thread reference to either the BarkRecognizer#recognize or the Remote#press_button methods (check Chapter 2 for a deeper thread discussion):

require 'dog_door.rb'
require 'remote.rb'
require 'bark_recognizer.rb'

door = DogDoor.new

remote = Remote.new(door)

recognizer = BarkRecognizer.new(door)

#Simulate the hardware hearing a bark
puts "Fido starts barking."
thread = recognizer.recognize("Woof")

puts "Fido has gone outside..."

puts "Fido's all done..."

sleep(3)

puts "...but he's stuck outside!"

puts "\nFido starts barking..."
thread = recognizer.recognize("Woof")


puts "Fido's back inside"

while thread.alive?
  sleep(0.1)
end

Of course we’re only testing the main path since we already tested the current alternate path in chapter 2.

And we’re done! The modified software can be delivered to our clients because we made sure it met their new specifications, and it all happened so easily because we made good use of requirements and a well-described use case to make it all clear.