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