не знаят какво е вътрешното устройство на другите обекти 1.class #=> Fixnum player.play(song) transaction.finish # def finish # @ammendable = false # ... # end
той няма single responsibility • Или да си поговорим за колела, а? • Знаете ли как работят скоростите? • ratio = chainring / cog.to_f • gear_inches = ratio * (rim + (tire *
cog, rim, tire) @chainring = chainring @cog = cog @rim = rim @tire = tire end def ratio chainring / cog.to_f # добра практика е да използваме accessor методи end def gear_inches ratio * (rim + (tire * 2)) end end • На какви съобщения отговаря Gear? • А как бихме го описали в едно изречение? Изречението съдържа
= data end def diameters #diameter знае, че 0 е rim, 1 е tire data.collect {|cell| cell[0] + (cell[1] * 2)} end end @data = [[622, 20], [622, 23], [559, 30], [559, 40]] # Списък от списъци • diameters знае твърде много!
= wheelify(data) end def diameters wheels.collect {|wheel| wheel.rim + (wheel.tire * 2)} end Wheel = Struct.new(:rim, :tire) # всеки може да праща rim/tire на wheel def wheelify(data) data.collect {|cell| Wheel.new(cell[0], cell[1])} end end • Всичко, което diameters знае е, че wheels връща enumerable • Ако списъкът се промени, трябва да променим само wheelify
2)} еnd def diameters # първо итерираме wheels.collect {|wheel| diameter(wheel)} еnd def diameter(wheel) # после калкулираме за едно колело wheel.rim + (wheel.tire * 2) end • Как да опишем diameters с едно изречение? • Трябва ли ни да намираме диаметър на точно едно колело?
def initialize(chainring, cog, rim, tire) @chainring = chainring @cog = cog @rim = rim @tire = tire end def gear_inches ratio * Wheel.new(rim, tire).diameter end # ... end Gear.new(52, 11, 26, 1.5).gear_inches • Gear знае за съществуването на Wheel, какви проблеми може да възникнат? class Gear attr_reader :chainring, :cog, :wheel def initialize(chainring, cog, wheel) @chainring = chainring @cog = cog @wheel = wheel end def gear_inches ratio * wheel.diameter end # ... end # Gear очаква „патка“, която знае'diameter' Gear.new(52,11,Wheel.new(26,1.5)).gear_inches
foo = some_intermediate_result * wheel.diameter #... Още редове сложна математика end • Външни съобщения – такива изпратени на друг (не на self) def gear_inches #... Няколко реда сложна математика foo = some_intermediate_result * diameter #... Още редове сложна математика end #... def diameter wheel.diameter end
обиколки с колела • Всяка обиколка следва определен маршрут, който има определена сложност • Клиентите могат да наемат колело за съответната обиколка • Колелата, запазени за всяка обиколка, трябва да са проверени и подготвени преди обиколката (напомпани гуми, проверени спирачки, скорости, и т.н.). За
'mechanic' може да бъде от всякакъв клас def prepare(mechanic) mechanic.prepare_bicycles(bicycles) end # ... end # ако подадем инстанция на класа долу, ще работи class Mechanic def prepare_bicycles(bicycles) bicycles.each {|bicycle| prepare_bicycle(bicycle)} end def prepare_bicycle(bicycle) #... end end • prepare метода няма експлицитна зависимост от класа механик • Да си представим, обаче, че нещата станат малко по-сложни
и шофьор • Координаторът осигурява необходимите неща за трипа, например храна, вода, нощувки и т.н. • Шофьорът трябва да се движи близо до основната група, отговорен е за багажа и е важен при инциденти • Всеки от тримата трябва да се подготви преди обиколката
attr_reader :bicycles, :customers, :vehicle def prepare(preparers) preparers.each do |preparer| case preparer when Mechanic preparer.prepare_bicycles(bicycles) when TripCoordinator preparer.buy_food(customers) when Driver preparer.gas_up(vehicle) preparer.fill_water_tank(vehicle) end end end end • Какви проблеми виждате? • Трябва ни конкретен клас за конкретно съобщение с конкретен аргумент • А ако се в бъдеще ни трябват още # когато вкараме TripCoordinator и Driver class TripCoordinator def buy_food(customers) # ... end end class Driver def gas_up(vehicle) #... end def fill_water_tank(vehicle) #... end end
една цел, трябва да подготви обиколката • Трябва да се абстрахираме от знанието ни за класовете и техните методи • Аргументите на prepare могат да си вършат работата, ако #prepare им вярва повече
attr_reader :bicycles, :customers, :vehicle def prepare(preparers) preparers.each {|preparer| preparer.prepare_trip(self)} end end # когато всеки подготвител е „патка“ # която отговаря на 'prepare_trip' class Mechanic def prepare_trip(trip) trip.bicycles.each {|bicycle| prepare_bicycle(bicycle)} # ... end class TripCoordinator def prepare_trip(trip) buy_food(trip.customers) end # ... end class Driver def prepare_trip(trip) vehicle = trip.vehicle gas_up(vehicle) fill_water_tank(vehicle) end # ... end
elsif preparer.kind_of?(Driver) preparer.gas_up(vehicle) preparer.fill_water_tank(vehicle) end Патките се крият от хората с колела, търсете ги там, където има: • case, който switch-ва в зависимост от клас • kind_of? или is_a? if preparer.responds_to?(:prepare_bicycles) preparer.prepare_bicycles(bicycle) elsif preparer.responds_to?(:buy_food) preparer.buy_food(customers) elsif preparer.responds_to?(:gas_up) preparer.gas_up(vehicle) preparer.fill_water_tank(vehicle) end