Tim Riley has a great rule of thumb for classifying objects and deciding what parameters should be part of the class' attributes (state) VS which should be provided as arguments to the methods of that class.
There are 2 types of Objects: Objects that "be" and Objects that "do".
If you say "this object IS a dog", then you are dealing with an object that "be".
The job of these objects are to represent data in our application. They hold data in their state and therefore we must
provide that data as initialization arguments
.
If you say "this object sends notifications" you are dealing with an object that DOES something.
They are typically initialized with configuration arguments only (including injected dependencies) and act on data that is given to them as arguments to their public instance methods.
class CurrencyConverter
attr_reader :from, :to, :external_conversion_service
# If you are confused by this way of doing defaults on dependency injection
# see https://gist.github.com/serodriguez68/29f6be8d51aceff3d1fd202ee4355bf2
def self.new(**args)
args[:external_conversion_service] ||= SomeExternalService.new
super(args)
end
def initialize(from:, to:, external_conversion_service:)
@from = from
@to = to
@external_conversion_service = external_conversion_service
end
# Amount is considered as "data" that is acted upon
def convert(amount)
external_conversion_service.process(from: from, to: to, amount)
end
end
# We configure the converter just one time
usd_to_euro_converter = CurrencyConverter.new(from: :usd, to: :euro)
# We can use this converter multiple times with different data
usd_to_euro_converter.convert(100)
usd_to_euro_converter.convert(1000)
Objects that "do": are intialized with stuff that should never need to change during the "lifecycle" of the object. In web-applications the "lifecycle" can be the web-response cycle or the booting of the program. In other words, if we have to throw away the object after calling it one time, we have probably intialized it the wrong way and we should be providing some stuff as function arguments instead of state.