As the responsibility of our applications grows, so too does our code. We'll walk through techniques of object-oriented programming that help clarify our intent and put responsibilities right where we need them.
A key, longstanding hallmark of a good program is that it separates what is stable from what changes in the interest of good maintenance. Saturday, June 1, 13
Account = Struct.new(:name, :balance) checking = Account.new(‘checking’,9000) savings = Account.new(‘savings’,5000) Transfer.new(checking, savings, 1000).execute We understand a use case through roles and interactions. Saturday, June 1, 13
class Transfer def initialize(source, destination, amount) @source, @destination, @amount = source.extend(Source), destination, amount end def execute source.transfer end module Source def transfer self.balance -= amount destination.balance += amount end end end Saturday, June 1, 13
class Transfer def initialize(source, destination, amount) @source, @destination, @amount = source.extend(Source), destination, amount end def execute source.transfer end module Source def transfer self.balance -= amount destination.balance += amount end end end Saturday, June 1, 13
class Transfer def initialize(source, destination, amount) @source, @destination, @amount = source.extend(Source), destination, amount end def execute source.transfer end module Source def transfer self.balance -= amount destination.balance += amount end end end source.extend(Source) Saturday, June 1, 13
class User include Casting::Client end user = User.find(3) # module Greeting def hello_world “Hello, from #{@name}” end end user.delegate(:hello_world, Greeting) #=> “Hello, from @saturnflyer” user.hello_world #=> NoMethodError Saturday, June 1, 13
class User include Casting::Client end user = User.find(3) # module Greeting def hello_world “Hello, from #{self.name}” end end user.delegate(:hello_world, Greeting) #=> “Hello, from @saturnflyer” user.hello_world #=> NoMethodError Saturday, June 1, 13
class User include Casting::Client end user = User.find(3) # module Greeting def hello_world “Hello, from #{self}” end end user.delegate(:hello_world, Greeting) #=> “Hello, from #user.hello_world #=> NoMethodError Saturday, June 1, 13
class User include Casting::Client delegate_missing_methods end user.hello_world #=> NoMethodError Casting.delegating(user => Greeting) do user.hello_world #=> “Hello, from @saturnflyer” end user.hello_world #=> NoMethodError Saturday, June 1, 13
class User include Casting::Client delegate_missing_methods end user.hello_world #=> NoMethodError Casting.delegating(user => Greeting) do user.hello_world #=> “Hello, from @saturnflyer” Casting.delegating(user => FormalGreeting) do user.hello_world #=> “Good day to you! from @saturnflyer” end end user.hello_world #=> NoMethodError Saturday, June 1, 13
class User include Casting::Client delegate_missing_methods end user.hello_world #=> NoMethodError user.cast_as(Greeting) user.hello_world #=> “Hello, from @saturnflyer” user.hello_world #=> “Hello, from @saturnflyer” user.uncast user.hello_world #=> NoMethodError Saturday, June 1, 13
class User include Casting::Client delegate_missing_methods end class UsersController < ApplicationController def show respond_with user.cast_as(UserRepresenter) end end Saturday, June 1, 13
Contextual Behavior Low Coupling High Cohesion Better Encapsulation Supports Our System Mental Model Separates Our Detail Mental Model Saturday, June 1, 13
class Transfer def initialize(source, destination, amount) @source, @destination, @amount = source.extend(Source), destination, amount end def execute source.transfer end module Source def transfer self.balance -= amount destination.balance += amount end end private_constant :Source end Saturday, June 1, 13
class Delegator < BasicObject kernel = ::Kernel.dup kernel.class_eval do [:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each do |m| undef_method m end end include kernel def self.const_missing(n) ::Object.const_get(n) end def initialize(obj) __setobj__(obj) Saturday, June 1, 13
class Delegator < BasicObject def respond_to_missing?(m, include_private) r = self.__getobj__.respond_to?(m, include_private) if r && include_private && !self.__getobj__.respond_to?(m, false) warn "#{caller(3)[0]}: delegator does not forward private method \##{m}" return false end r end def methods(all=true) __getobj__.methods(all) | super end def public_methods(all=true) Saturday, June 1, 13
class Delegator < BasicObject def methods(all=true) __getobj__.methods(all) | super end def public_methods(all=true) __getobj__.public_methods(all) | super end def protected_methods(all=true) __getobj__.protected_methods(all) | super end def ==(obj) return true if obj.equal?(self) Saturday, June 1, 13
class Delegator < BasicObject def ==(obj) return true if obj.equal?(self) self.__getobj__ == obj end def !=(obj) return false if obj.equal?(self) __getobj__ != obj end def ! !__getobj__ end Saturday, June 1, 13
class Delegator < BasicObject def marshal_load(data) version, vars, values, obj = data if version == :__v2__ vars.each_with_index{|var, i| instance_variable_set(var, values[i])} __setobj__(obj) else __setobj__(data) end end def initialize_clone(obj) self.__setobj__(obj.__getobj__.clone) end def initialize_dup(obj) self.__setobj__(obj.__getobj__.dup) Saturday, June 1, 13
class Delegator < BasicObject def initialize_clone(obj) self.__setobj__(obj.__getobj__.clone) end def initialize_dup(obj) self.__setobj__(obj.__getobj__.dup) end private :initialize_clone, :initialize_dup [:trust, :untrust, :taint, :untaint, :freeze].each do |method| define_method method do __getobj__.send(method) super() end end Saturday, June 1, 13
class Delegator < BasicObject [:trust, :untrust, :taint, :untaint, :freeze].each do |method| define_method method do __getobj__.send(method) super() end end @delegator_api = self.public_instance_methods def self.public_api @delegator_api end end Saturday, June 1, 13