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