programming-examples/ruby/_Basics/single_responsibility.rb

93 lines
2.6 KiB
Ruby
Raw Normal View History

2019-11-15 12:59:38 +01:00
# The Single Responsibility Principle is the most abstract of the bunch. It
# helps keep classes and methods small and maintainable. In addition to keeping
# classes small and focused it also makes them easier to understand.
# While we all agree that focusing on a single responsibility is important, its
# difficult to determine what a classs responsibility is. Generally, it is said
# that anything that gives a class a reason to change can be viewed as a
# responsibility. By change I am talking about structural changes to the class
# itself (as in modifying the code in the classs file, not the objects
# in-memory state).
# In the below class we have a single command interface that processes
# commission payments for deals. At first glance the class seems simple enough,
# but lets look at reasons we might want to change this class. Any change in
# how we calculate commissions would require a change to this class. We could
# introduce new commission rules or strategies that would cause our
# calculate_commission method to change. For instance, we might want to vary
# the percentage based on deal amount. Any change in the steps required to mark
# a deal as processed in the mark_deal_processed method would result in a change
# in the file as well. An example of this might be adding support for sending an
# email summary of a specific persons commissions after marking a deal
# processed. The fact that we can identify multiple reasons to change signals a
# violation of the Single Responsibility Principle.
class DealProcessor
attr_reader :deals
def initialize(deals)
@deals = deals
end
def process
deals.each do |deal|
# Here we calculate commission and create instance of Commission
Commission.create(deal: deal, amount: calculate_commission(deal))
mark_deal_processed
end
end
private
def mark_deal_processed
# Implementation
end
def calculate_commission(deal)
deal.amount * 0.05
end
end
class Commission
# Implementation
end
# Solution
class DealProcessor
attr_reader :deals
def initialize(deals)
@deals = deals
end
def process
deals.each do |deal|
# Now we call calculator in one operation, all logic now in it
CommissionCalculator.create_commission(deal) if mark_deal_processed
end
end
private
def mark_deal_processed
# Implementation
end
end
class CommissionCalculator
def self.create_commission(deal)
Commission.new(deal: deal, amount: calculate(deal))
end
private
def self.calculate(deal)
deal.amount * 0.05
end
end
class Commission
# Implementation
end