Slide 1

Slide 1 text

Learnings from Practical Object-Oriented Design in Ruby https://www.poodr.com/

Slide 2

Slide 2 text

Object-Oriented Software ● Software built with objects communicating by sending messages to achieve desired behaviour. ● Mindset shift - procedural => Object- oriented. ● Each app = unique programming language tailored specifically for your domain

Slide 3

Slide 3 text

Object-Oriented Design ● Mindset shift : procedural => Object-oriented ● Assembly line => art studio ● Biggest problem is Change ● More about achieving changeability than perfection

Slide 4

Slide 4 text

=> Reusable classes = pluggable units of well-defined behaviour

Slide 5

Slide 5 text

Single Responsibility class Gear def initialize(chainring, cog) @chainring = chainring @cog = cog end def ratio @chainring / @cog.to_f end end puts Gear.new(52, 11).ratio puts Gear.new(30, 27).ratio class Gear def initialize(chainring, cog, rim, tire) @chainring = chainring @cog = cog @rim = rim @tire = tire end def ratio @chainring / @cog.to_f end def gear_inches ratio * (@rim + (@tire * 2)) end end Adds 2 new arguments which breaks existing clients

Slide 6

Slide 6 text

Solutions class Gear attr_reader :chainring, :cog, :rim, :tire def initialize(chainring:, cog:, rim: nil, tire: nil) @chainring = chainring @cog = cog @rim = rim @tire = tire end def ratio chainring / cog.to_f end def gear_inches ratio * (rim + (tire * 2)) end end class Gear attr_reader :chainring, :cog, :wheel def initialize(chainring:, cog:, rim: nil, tire: nil) @chainring = chainring @cog = cog @wheel = Wheel.new(rim, tire) end def ratio chainring / cog.to_f end def gear_inches ratio * wheel.diameter end Wheel = Struct.new(:rim, :tire) do def diameter rim + (tire * 2) end def circumference diameter * Math::PI end end end Isolate problematic code using Struct Always access instance variables via getters. Makes it easy to add logic to getters without changing everywhere instance variable is used.

Slide 7

Slide 7 text

Identify classes 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) Gear.new(52, 11, wheel).gear_inches Gear.new(52, 11).ratio Dependency Injection Can be anything that responds to diameter Cylinder, Disk, etc.

Slide 8

Slide 8 text

Summarizing Options ● Inject dependencies ● Remove argument order dependency ● Isolate dependencies ● Extract classes

Slide 9

Slide 9 text

Designing Interfaces ● Interface = glue that holds together objects ● Expose as little about yourself as possible Know as little about others as possible ● Public - used by others, rarely changes, well tested ● Private - not used by others, frequent changes, not tested directly

Slide 10

Slide 10 text

Messages ● Focus on messages - not data ● Messages create dependencies

Slide 11

Slide 11 text

Sequence Diagrams ● Quick and easy way to prototype, erase, redo ● Gives clear idea about classes and their interactions ● Reveal new (sometimes abstract) classes which were not foreseen ● Ask what you need instead of telling how to do

Slide 12

Slide 12 text

Sequence Diagrams Trip knows a lot about Mechanic

Slide 13

Slide 13 text

Organizing Interfaces

Slide 14

Slide 14 text

Sequence Diagrams Customer should not be iterating on each trip and finding suitable bicycle. They just want a “planned” trip.

Slide 15

Slide 15 text

Discovering new class

Slide 16

Slide 16 text

Law of Demeter customer.bicycle.wheel.rotate ● Try not to have chained method calls ● Reaching far out objects ● Knowing too much Exceptions ● Intermediate values are of same type. ● No mutation - you don’t own intermediate objects so you shouldn’t be mutating them User.where(status: :active) .where(paid: true) .order(:created_at) Such chaining of methods is fine because it follows both exceptions