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.
Top comments (2)
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.
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