DEV Community

loading...

Explaining S.O.L.I.D principles with Ruby code examples

Mbonu Blessing
Full Stack developer @Execonline and @Andela | Languages: Javascript and Ruby | Libraries: React and Ruby on Rails
Updated on ・4 min read

Hello everyone, hope you week went well?

This week I will be giving code examples in ruby with reference to S.O.L.I.D principles explained in this article, The S.O.L.I.D Principles in Pictures by Ugonna Thelma. This is the best I have seen so far that explains it with clear images.

So what I am going to do it kind of show just code examples of her illustration in Ruby.

S — Single Responsibility

# don't do this

class JackOfAllTrade
  def initialize(name)
    @name = name.capitalize
  end

  attr_reader :name

  def cook
   "On it. #{name} is cooking your favorite food"
  end

  def garden
    "#{name} is planting new flowers in the garden"
  end

  def paint
    "#{name} is painting the walls in the sitting room"
  end

  def drive
    "#{name} is driving you to the airport"
  end
end

ngozi = JackOfAllTrade.new('Ngozi')
puts ngozi.cook
puts ngozi.garden
puts ngozi.paint
puts ngozi.drive

# => On it. Ngozi is cooking your favorite food
# => Ngozi is planting new flowers in the garden
# => Ngozi is painting the walls in the sitting room
# => Ngozi is driving you to the airport
# do this instead

class Chef
  def initialize(name)
    @name = name.capitalize
  end

  attr_reader :name

  def cook
   "On it. #{name} is cooking your favorite food"
  end
end

class Gardener
  def initialize(name)
    @name = name.capitalize
  end

  attr_reader :name

  def garden
    "#{name} is planting new flowers in the garden"
  end
end

class Painter
  def initialize(name)
    @name = name.capitalize
  end

  attr_reader :name

  def paint
    "#{name} is painting the walls in the sitting room"
  end
end

class Driver
  def initialize(name)
    @name = name.capitalize
  end

  attr_reader :name

  def drive
    "#{name} is driving you to the airport"
  end
end

ngozi_the_chef = Chef.new('Ngozi')
ngozi_the_gardener = Gardener.new('Ngozi')
ngozi_the_painter = Painter.new('Ngozi')
ngozi_the_driver = Driver.new('Ngozi')

puts ngozi_the_chef.cook
puts ngozi_the_gardener.garden
puts ngozi_the_painter.paint
puts ngozi_the_driver.drive

# => On it. Ngozi is cooking your favorite food
# => Ngozi is planting new flowers in the garden
# => Ngozi is painting the walls in the sitting room
# => Ngozi is driving you to the airport

# these should throw an `undefined method` error because it doesn't perform that function

puts ngozi_the_chef.garden
# => undefined method `garden' for #<Chef:0x00007ff4918305d0 @name="Ngozi"> (NoMethodError)

# puts ngozi_the_gardener.paint
# puts ngozi_the_painter.drive
# puts ngozi_the_driver.cook

O — Open-Closed

# don't do this

class Chef
  def initialize(name)
    @name = name.capitalize
    @description = 'cooking a delicious meal'
  end

  def cook
    "#{@name} is #{@description}"
  end

  attr_writer :description
end

ngozi_the_chef = Chef.new('ngozi')
puts ngozi_the_chef.cook
ngozi_the_chef.description = 'painting the house'
puts ngozi_the_chef.cook

# => Ngozi is cooking a delicious meal
# => Ngozi is painting the house
# do this instead

class Chef
  def initialize(name)
    @name = name.capitalize
  end

  def cook
    "#{@name} is cooking a delicious meal"
  end
end

class ChefAndPainter < Chef
  def paint
    "#{@name} is painting the walls in the sitting room"
  end
end

ngozi_the_chef = Chef.new('ngozi')
puts ngozi_the_chef.cook
ngozi_the_chef_and_painter = ChefAndPainter.new('ngozi')
puts ngozi_the_chef_and_painter.cook
puts ngozi_the_chef_and_painter.paint

# => Ngozi is cooking a delicious meal
# => Ngozi is cooking a delicious meal
# => Ngozi is painting the walls in the sitting room

L — Liskov Substitution

# don't do this

class Server
  def initialize(name)
    @name = name.capitalize
  end

  attr_reader :name

  def serve_coffee
   "I am #{name}. Here is your coffee"
  end

  def serve_water
    "I am #{name}. Here is your water"
  end
end

class ServerChild < Server
  undef_method :serve_coffee
end

lade = Server.new('lade')
puts lade.serve_coffee
evans = ServerChild.new('evans')
puts evans.serve_water
puts evans.serve_coffee

# => I am Lade. Here is your coffee
# => I am Evans. Here is your water
# => Traceback (most recent call last):
# => test.rb:45:in `<main>': undefined method `serve_coffee' for #<ServerChild:0x00007fc4ee851848 @name="Evans">
# do this instead

class Server
  def initialize(name)
    @name = name.capitalize
  end

  attr_reader :name

  def serve_coffee
   "I am #{name}. Here is your coffee"
  end

  def serve_water
    "I am #{name}. Here is your water"
  end
end

class ServerChild < Server
  def serve_coffee
    "I am #{name}. Here is your cappucino"
  end
end

lade = Server.new('lade')
puts lade.serve_coffee
evans = ServerChild.new('evans')
puts evans.serve_water
puts evans.serve_coffee

# => I am Lade. Here is your coffee
# => I am Evans. Here is your water
# => I am Evans. Here is your cappucino

I — Interface Segregation

# don't do this

class Robot
  def initialize
    @no_of_arms = 2
    @no_of_antennas = 4
  end

  def spin_around
    'I can spin around'
  end

  def rotate_arm
    "I am rotating my #{@no_of_arms} arms"
  end

  def paint_house
    "Painting the house with my painting brush arm"
  end

  def search_for_stations
    "#{@no_of_antennas} antennas connecting to the closest radio station"
  end
end

class PainterRobot < Robot
end

class RadioRobot < Robot
end

# RadioRobot should not know anything about painting and PainterRobot shouldn't concern itself with searching for stations

puts RadioRobot.new.paint_house
puts PainterRobot.new.search_for_stations

# => Painting the house with my painting brush arm
# => 4 antennas connecting to the closest radio station
# do this

class Robot
  def initialize
    @no_of_arms = 2
  end

  def spin_around
    'I can spin around'
  end

  def rotate_arm
    "I am rotating my #{@no_of_arms} arms"
  end
end

class PainterRobot < Robot
  def paint_house
   "Painting the house with my painting brush arm"
  end
end

class RadioRobot < Robot
  def initialize
    @no_of_antennas = 4
  end

  def search_for_stations
    "#{@no_of_antennas} antennas connecting to the closest radio station"
  end
end

puts RadioRobot.new.search_for_stations
puts PainterRobot.new.paint_house

# => 4 antennas connecting to the closest radio station
# => Painting the house with my painting brush arm

D — Dependency Inversion

# don't do this

class Robot
  def initialize
    @no_of_arms = 2
  end

  def spin_around
    'I can spin around'
  end

  def rotate_arm
    "I am rotating my #{@no_of_arms} arms"
  end
end

class PainterRobot < Robot
  def paint_house
   "Painting the house with my painting brush arm"
  end
end

puts PainterRobot.new.paint_house

# we want it to be able to paint with whatever tool we give it
# => Painting the house with my painting brush arm
# do this 

class Robot
  def initialize
    @no_of_arms = 2
  end

  def spin_around
    'I can spin around'
  end

  def rotate_arm
    "I am rotating my #{@no_of_arms} arms"
  end
end

class PainterRobot < Robot
  def initialize(tool)
    @tool = tool
  end

  def paint_house
   "Painting the house with my #{@tool} arm"
  end
end

puts PainterRobot.new('painting brush').paint_house
puts PainterRobot.new('paint sprayer').paint_house
puts PainterRobot.new('paint roller').paint_house

# => Painting the house with my painting brush arm
# => Painting the house with my paint sprayer arm
# => Painting the house with my paint roller arm

That's it for this article. You can read through the main article to understand the concepts more.

Leave your comments and thoughts below. I would love to read them.

Until next week.

Discussion (2)

Collapse
thorstenhirsch profile image
Thorsten Hirsch

Your examples make it as clear as the blue sky. Deserves much more views!

There's just a little mistake in the Server's serve_coffee, it should say "Here is your coffee". It is correct in the comments of the test code, but it's wrong ("...cappuccino") in the method implementation.

Collapse
nkemjiks profile image
Mbonu Blessing Author

Thanks for noticing the errors and the kind words. That mean alot. But the mistake is from the method implementation. It should be coffee not cappuccino. The point is that as a child, you should have the same method as the parent and can serve something similar as the parent class method. Cappuccino is a variation of coffee. I wouldn't need to override the parent method if I wanted to do exactly the same thing