Last active
December 25, 2015 19:19
-
-
Save tsnow/7027362 to your computer and use it in GitHub Desktop.
An Example of the Null Object Pattern
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This class throws a NoMethod exception in #active_for_user? when it's initialized | |
# with a ride which has no user. We'd like to just turn the class's behaviour off in that case | |
# since the whole functionality of it really doesn't make sense for rides which are userless. | |
# Usage: PimEventSubscription.new(ride).subscribe/unsubscribe | |
class PimEventSubscription | |
attr_reader :ride | |
def initialize(ride) | |
@ride=ride | |
end | |
def subscribe | |
log_active | |
return unless active_for_user? | |
"subscribe" | |
end | |
def unsubscribe | |
log_active | |
return unless active_for_user? | |
"unsubscribe" | |
end | |
private | |
def log_active | |
return unless ride.user.present? | |
Rails.logger.error("#{prefix} Activated for User:#{Features[ride.user].id}:#{active_for_user?.inspect}") | |
end | |
def active_for_user? | |
Features[ride.user].pim_event_feed_enabled? # explodes when ride.user is null | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# We can certainly fix it by adding if statements through out the entire class in all the areas affected... | |
# But that leads to painful duplication of branching. | |
# Usage: PimEventSubscription.new(ride).subscribe/unsubscribe | |
class PimEventSubscription | |
attr_reader :ride | |
def initialize(ride) | |
@ride=ride | |
end | |
def subscribe | |
log_active | |
return unless active_for_user? | |
"subscribe" | |
end | |
def unsubscribe | |
log_active | |
return unless active_for_user? | |
"unsubscribe" | |
end | |
private | |
def log_active | |
return unless ride.user.present? #EW. We had to fix it in two places. | |
Rails.logger.error("#{prefix} Activated for User:#{Features[ride.user].id}:#{active_for_user?.inspect}") | |
end | |
def active_for_user? | |
return if ride.user.present? #EW. Duplication usually means you're missing something shared. | |
Features[ride.user].pim_event_feed_enabled? | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Lets solve that duplication by using the Null Object Pattern | |
# If we make an object which responds to all the same methods that | |
# the primary object responds to _from its external callers_... | |
class InvalidPimEventSubscription | |
def subscribe | |
end | |
def unsubscribe | |
end | |
end | |
class PimEventSubscription | |
def self.for_ride(ride) | |
return PimEventSubscription.new(ride) if ride.user.present? | |
return InvalidPimEventSubscription.new #Then callers wont be the wiser. | |
end | |
# ... rest of original (step 0) PimEventSubscription code here. | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# And we can use the new for_ride method here, and see how the null objects have the same protocol as the primary objects. | |
def enable_rips_gps_updates | |
#- PimEventSubscription.new(self).subscribe | |
PimEventSubscription.for_ride(self).subscribe | |
update_attribute(:rips_hook_enabled, RipsHook.register_gps(self)) | |
end | |
def disable_rips_gps_updates | |
#- PimEventSubscription.new(self).unsubscribe | |
PimEventSubscription.for_ride(self).unsubscribe | |
RipsHook.deregister_gps(self) | |
# even if the deregister fails proceed as though | |
# it succeeded... | |
update_attribute(:rips_hook_enabled, false) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment