Share
Embed
Intro to Object Oriented Programming in Ruby
An introduction to Object Oriented Programming in Ruby, presented by Peter Brown at the Burlington Ruby Group
Peter Brown
September 20, 2012
Transcript
39 slides
-
Intro to Object Oriented Programming* *in Ruby Burlington Ruby -
September 2012 - Peter Brown -
Introduction OOP Overview Designing Objects Testing Refactoring OOP Tips class
Presentation def language 'Ruby' end def topic 'OOP' end def totally_awesome? true end end -
Procedural Programming Model a sequence of events Data and functions
(procedures) are separate Perfect for procedural languages class DataProcessor def process data = get_data_from_file new_data = convert_to_format(data) write_data_to_file(new_data) end def get_data_from_file end def convert_to_format(data) end def write_data_to_file(new_data) end end processor = DataProcessor.new processor.process -
What can be improved? Procedural programming alone can be: difficult
to relate to real world objects tough to read and modify hard to test -
Objects to the rescue! OOP is a shift in thinking
Dividing code into small classes with a single responsibility: React to change Reuse code Self documenting -
What are Objects? Instance of a class Properties / State
Instance variables (@var) Methods: Command vs Query -
Everything in Ruby is an Object String Integer Float Symbol
Method Array Hash File Regexp Exception Boolean -
Object Example pete = Person.new pete.age = 30 pete.age #
=> 30 class Person def age=(value) @age = value end def age @age end end Class vs Object -
Interface (API) Public methods exposed to consumers Consumers = developers
or code Hide implementation (Encapsulation) -
Public Methods [:!, :!=, :! ~, :<=>, :==, :===, :=~,
:__id__, :__send__, :class, :clone, :define_singleton_method, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :initialize_clone, :initialize_dup, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :kind_of?, :method, :methods, :nil?, :object_id, :private_methods, :protected_methods, :public_method, :public_methods, :public_send, :respond_to?, :respond_to_missing?, :send, :singleton_class, :singleton_methods, :taint, :tainted?, :tap, :to_enum, :to_s, :trust, :untaint, :untrust, :untrusted?] class Person; end Person.new.methods # => -
Interfaces: Good vs Bad Predictable Familiar Consistent Dependable Hide implementation
Unpredictable Unfamiliar Inconsistent Subject to change Expose implementation -
Interfaces: A Comparison jack = Person.new('Jack', 35, 'brown') jill =
Person.new('Jill', 31, 'red') -
Interfaces: A Comparison jack = Person.new('Jack', 35, 'brown') jill =
Person.new('Jill', 31, 'red') jack = Person.new(name: 'Jack', age: 35, hair: 'brown') jill = Person.new(age: 31, name: 'Jill', hair: 'red') bill = Person.new(age: 40, name: 'Bill') -
Predictable Interfaces jack.name # => ‘Jack’ jack.hair # => ‘brown’
jack.male? # => true jack.siblings.size # => 2 jack.reset! # => Dangerous! -
Classical Inheritance Object A is an Object B All objects
have a superclass Methods automatically delegated to superclass class Beverage end class Beer < Beverage end -
Classical Inheritance class Alarm def check notify if active? end
def notify puts "#{self.class} is active!" end def active? raise NotImplementedError end end -
Classical Inheritance class FireAlarm < Alarm def active? fire_detected? end
private def fire_detected? true end end alarm = FireAlarm.new alarm.check # => FireAlarm is active! -
Sharing via Modules Object A behaves like Object B Can
be reused by other classes Classes can include multiple modules module Drinkable def drink puts "mmmm" end end class Beer include Drinkable end -
Sharing via Modules class Alarm include Notifiable def check notify
if active? end def active? raise NotImplementedError end end module Notifiable def notify puts "#{self.class} is active!" end end -
Composition Object A has an Object B Create objects that
are composed of other objects class Roof end class House def roof Roof.new end end -
Composition 20.meters.to_feet # => 65.6168 class Meter def initialize(value) @value
= value end def to_feet @value * 3.28084 end end -
Composition 20.meters.to_feet # => 65.6168 class Meter def initialize(value) @value
= value end def to_feet @value * 3.28084 end end class Numeric def meters Meter.new(self) end end -
Composition class Numeric def feet Foot.new(self) end def meters Meter.new(self)
end end 20.feet.to_feet # => 20 20.meters.to_feet # => 65.6168 -
Composition house = Building.new(height: 6.meters) house.height.to_feet # => 19.68504 skyscraper
= Building.new(height: 500.feet) skyscraper.height.to_feet # => 500 -
Composition class Building attr_reader :height def initialize(options={}) @height = options[:height]
end end house = Building.new(height: 6.meters) house.height.to_feet # => 19.68504 skyscraper = Building.new(height: 500.feet) skyscraper.height.to_feet # => 500 -
Unit Testing Interfaces & BDD go hand in hand Makes
refactoring less painful Testing Public vs Private methods -
Unit Testing: RSpec describe Person do subject { Person.new(name: 'Jack')
} context '#siblings' do it { should have(:no).siblings } specify 'adding siblings behaves like an array' do subject.siblings << Person.new(name: 'Jill') subject.siblings << Person.new(name: 'Bill') subject.should have(2).siblings end end end -
Code Smells Abusing Inheritance Defensive coding Class names that end
in ‘er’ and `or` Complex tests Arrays representing objects Class uses methods from another class excessively Too many parameters Large classes Long methods -
OOP Tips Avoid unexpected state change Think in terms of
what an object does, not what it is (BDD) Don’t have to start with objects, add them as needed Code to an interface Be consistent Favor composition over inheritance Avoid tight coupling -
ClassyEnum Gem Combines classes, enums and ActiveRecord Composition and Polymorphism
https://github.com/beerlington/classy_enum