Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Following the Path of Programs

Jim Gay
June 01, 2013

Following the Path of Programs

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.

Jim Gay

June 01, 2013
Tweet

More Decks by Jim Gay

Other Decks in Technology

Transcript

  1. If you do not enter the tiger’s cage, you will

    not catch its cub Saturday, June 1, 13
  2. “The best documentation is the source file” — Eddie Kao,

    'Code Reading - Learning More about Ruby by Reading Ruby Source Code' Saturday, June 1, 13
  3. It’s not what an object is that matters, it’s what

    it does — Sandi Metz, Practical Object Orented Design in Ruby Saturday, June 1, 13
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. Source.new(account) source Account Object Kernel BasicObject Account Object Kernel BasicObject

    Source SimpleDelegator Delegator #<Module> BasicObject Saturday, June 1, 13
  10. class User include Casting::Client end user = User.find(3) #<User:0x007fc673393e88 @name=”@saturnflyer”>

    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
  11. class User include Casting::Client end user = User.find(3) #<User:0x007fc673393e88 @name=”@saturnflyer”>

    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
  12. class User include Casting::Client end user = User.find(3) #<User:0x007fc673393e88 @name=”@saturnflyer”>

    module Greeting def hello_world “Hello, from #{self}” end end user.delegate(:hello_world, Greeting) #=> “Hello, from #<User:0x007f8d4b02d9 user.hello_world #=> NoMethodError Saturday, June 1, 13
  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
  14. 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
  15. 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
  16. 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
  17. Contextual Behavior Low Coupling High Cohesion Better Encapsulation Supports Our

    System Mental Model Separates Our Detail Mental Model Saturday, June 1, 13
  18. 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
  19. 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
  20. class Delegator < BasicObject def self.const_missing(n) ::Object.const_get(n) end def initialize(obj)

    __setobj__(obj) end def method_missing(m, *args, &block) target = self.__getobj__ begin target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &block ensure [email protected]_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@ Saturday, June 1, 13
  21. class Delegator < BasicObject def initialize(obj) __setobj__(obj) end def method_missing(m,

    *args, &block) target = self.__getobj__ begin target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &block ensure [email protected]_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@ end end def respond_to_missing?(m, include_private) Saturday, June 1, 13
  22. class Delegator < BasicObject def method_missing(m, *args, &block) target =

    self.__getobj__ begin target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &b ensure [email protected]_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@ end end 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 Saturday, June 1, 13
  23. 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
  24. 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
  25. 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
  26. class Delegator < BasicObject def __getobj__ raise NotImplementedError, "need to

    define `__getobj__'" end def __setobj__(obj) raise NotImplementedError, "need to define `__setobj__'" end def marshal_dump ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var} [ :__v2__, ivars, ivars.map{|var| instance_variable_get(var)}, __getobj__ ] Saturday, June 1, 13
  27. class Delegator < BasicObject def marshal_dump ivars = instance_variables.reject {|var|

    /\A@delegate_/ =~ var} [ :__v2__, ivars, ivars.map{|var| instance_variable_get(var)}, __getobj__ ] end 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 Saturday, June 1, 13
  28. 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
  29. 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
  30. 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