class ObscuringReferences attr_reader :data
def initialize(data)
@data = data end
def diameters
# 0 is rim, 1 is tire
data.collect {|cell| cell[0] + (cell[1] * 2)}
end
# ... many other methods that index into the array
end
# rim and tire sizes (now in millimeters!) in a 2d array
@data = [[622, 20], [622, 23], [559, 30], [559, 40]]
class RevealingReferences
attr_reader :wheels
def initialize(data)
@wheels = wheelify(data)
end
def diameters
wheels.collect {|wheel| wheel.rim + (wheel.tire * 2)}
end
# ... now everyone can send rim/tire to wheel
Wheel = Struct.new(:rim, :tire)
def wheelify(data)
data.collect {|cell| Wheel.new(cell[0], cell[1])}
end
end
The diameters method above now has no knowledge of the internal structure of the array. All diameters knows is that the message wheels returns an enumerable and that each enumerated thing responds to rim and tire. What were once references to cell[1] have been transformed into message sends to wheel.tire.
class Gear
attr_reader :chainring, :cog, :wheel
def initialize(chainring, cog, wheel=nil)
@chainring = chainring
@cog = cog
@wheel = wheel
end
def ratio
chainring / cog.to_f
end
def gear_inches
ratio * wheel.diameter
end
end
class Wheel
attr_reader :rim, :tire
def initialize(rim, tire)
@rim = rim
@tire = tire
end
def diameter
rim + (tire * 2)
end
def circumference
diameter * Math::PI
end
end
@wheel = Wheel.new(26, 1.5) puts @wheel.circumference
# -> 91.106186954104
puts Gear.new(52, 11, @wheel).gear_inches
# -> 137.090909090909
puts Gear.new(52, 11).ratio
# -> 4.72727272727273
The path to changeable and maintainable object-oriented software begins with classes that have a single responsibility. Classes that do one thing isolate that thing from the rest of your application. This isolation allows change without consequence and reuse without duplication.