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

Making Little Classes out of Big Ones

avdi
May 23, 2012

Making Little Classes out of Big Ones

A Lunch & Learn talk I gave at Hashrocket, talking about the pros & cons of four different tehniques for breaking too-big classes into smaller pieces.

avdi

May 23, 2012
Tweet

More Decks by avdi

Other Decks in Programming

Transcript

  1. One class to rule them all class User < ActiveRecord::Base

    # social media-related methods def find_friends; ...; end def spam_social_networks(message); end def recent_status; ...; end # authorization-related methods... # account-related methods... # content-related methods # A hojillion more methods... end
  2. Extract Module class User < ActiveRecord::Base include Sociability # authorization-related

    methods... # account-related methods... # content-related methods # A hojillion more methods... end
  3. Now you have two problems The computer doesn’t care how

    big your class is But you have limited headspace Out of sight is not out of mind Just pushing the problem around. Better to keep the pain obvious!
  4. Extract Decorator require ’delegate’ class SociableUser < DelegateClass(User) # social

    media-related methods def find_friends; ...; end def spam_social_networks(message); end def recent_status; ...; end end
  5. Decorator Advantages Has its own private namespace for ivars Easy

    to test in isolation Respects public API of wrapped object Can be used anywhere original was used
  6. Decorator Problems Tough to make perfectly interchangeable original = User.find(id)

    wrapped = SociableUser.new(user) user.class # => SociableUser original.object_id == wrapped.object_id # => false Wrapped object can’t use decorated methods
  7. Wrapped object can’t use decorated methods class User < ActiveRecord::Base

    def bio "Just another user..." end def summary " #{ name } : #{ bio } " end end
  8. Wrapped object can’t use decorated methods wrapped_user.bio # => "80%

    angel, 10% daemon, [...]." wrapped_user.summary # => "Avdi: Just another user..."
  9. Extracted Role Advantages Only added where needed, unlike included module

    Object “sees” updated methods Can “re-wire” the object from within
  10. Extracted Role Problems Can “re-wire the object from within No

    isolation: can depend on private methods No private variable namespace Can’t be undone to a gvien object May have a performance penalty
  11. Composition class SocialUser def initialize(data) @data = data end def

    find_friends; ...; end def spam_social_networks(message); end def recent_status; ...; end end
  12. Composition class User < ActiveRecord::Base def social_user SocialUser.new(self) end delegate

    :find_friends , :spam_social_networks , :to => :social_user end
  13. Composition Notes A circular arrangement User delegates management of twitter_handle,

    facebook_id, etc. fields to social_user. SocialUser uses its @user reference only as a data object for those fields.
  14. Breaking the circle class User < ActiveRecord::Base has_one :social_user delegate

    :find_friends , :spam_social_networks , :to => :social_user end
  15. Composition Advantages Easy to understand Easy to test Can be

    introduced with no change to external usage A gateway to bigger refactorings
  16. Guidelines Use module inclusion for internal, cross-cutting concerns. E.g. Loggable.

    Use decoration for behavior layered “on top” (e.g. presentation) Use decoration if your extension has its own state. Use module extension for dynamically building up an object with several variable aspects.
  17. Guidelines Use module extension as a cleaner form of monkeypatching.

    Prefer decoration to module extension. Use composition for spinning off distinct responsibilities into separate objects. ???