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
Extract Module class User < ActiveRecord::Base include Sociability # authorization-related methods... # account-related methods... # content-related methods # A hojillion more methods... end
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!
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
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
Wrapped object can’t use decorated methods class User < ActiveRecord::Base def bio "Just another user..." end def summary " #{ name } : #{ bio } " end end
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
Composition class SocialUser def initialize(data) @data = data end def find_friends; ...; end def spam_social_networks(message); end def recent_status; ...; end end
Composition class User < ActiveRecord::Base def social_user SocialUser.new(self) end delegate :find_friends , :spam_social_networks , :to => :social_user end
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.
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.
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. ???