The Single Responsibility Principle

The Single Responsibility Principle is used to keep classes or methods compact and easy to understand. It allows the objects to be responsible for only one thing: anything that would cause the class to change. It may help to look at some code to illustrate this:

class Airport

  DEFAULT_CAPACITY = 10

  attr_reader :planes, :capacity

  def initialize()
    @planes = []
    @capacity = DEFAULT_CAPACITY

  end

  def new_capacity=(capacity)
    @capacity = capacity
  end

  def land(plane)
    fail "It is stormy" if stormy?
    fail "This airport is full" if full?
    fail "This plane is landed" if plane.flying == false
    @planes << plane
  end

  def takeoff(_plane)
    fail "It is stormy" if stormy?
    @planes.pop
  end

  private

  def full?
    @planes.length >= DEFAULT_CAPACITY
  end

  def stormy?
    (rand(50) == 25) ? @stormy = true : @stormy = false
  end

end

class Plane  
  # Plane methods
end

The code above is a relatively simple example of an airport traffic control system. Planes can land and takeoff from an airport as long as there is space and it is not stormy. However, in terms of the single responsibility principle we can see should the airport be responsible for calculating the weather?

A simple way of deciding is to ask the question "Can an airport tell a plan to takeoff?" Sounds Reasonable. "Can an airport decide what the weather will be like? Maybe that is a stretch too far. In terms of design this is probably the moment to decide to create a new class.

Why should we do this? Because although this is a simple program that can survive completely well on its own we have no way of being able to anticipate the design decisions of the future. Our weather model may expand exponentially, our user may require a far greater level of control over the airport. Creating code where each unit has a single responsibility means that we are in the best possible position to respond to the demands of the future. In short as our code base grows its maintenance and extension are easy and enjoyable to program rather than a back-breaking labor of pain.

So that being said here is a look at how our code could look at this point adhering to the Single Responsibility Principle.

class Airport

  DEFAULT_CAPACITY = 10

  attr_reader :planes, :capacity

  def initialize(weather)
    @planes = []
    @capacity = DEFAULT_CAPACITY
    @weather = weather || Weather.new
  end

  def new_capacity=(capacity)
    @capacity = capacity
  end

  def land(plane)
    fail "It is stormy" if @weather.stormy?
    fail "This airport is full" if full?
    fail "This plane is landed" if plane.flying == false
    @planes << plane
  end

  def takeoff(_plane)
    fail "It is stormy" if @weather.stormy?
    @planes.pop
  end

  private

  def full?
    @planes.length >= DEFAULT_CAPACITY
  end

end

class Weather

  def stormy?
    (rand(50) == 25) ? @stormy = true : @stormy = false
  end

end