Interesting. I'm having trouble knowing how small to make my classes. I've seen different articles saying you should only have one public method per class.
Should I create one PriceCalculator service where I can ask it for things like sale_price, material_cost, fixed cost, variable_cost, production_cost...
Or should I make independant classes for each of these? That way sale_price would be one calling the other classes to get to the final price.
My biggest concern separating them all into classes is that material_cost, for example, is is called in different classes, repeting calculations that I have already done.
Under one big SalePrice class, I avoided calculating the same thing multiple times because I would set class variables that would be accessible by all methods.
I usually anticipate having more than one such calculator, because very often, pricing is a classic case for the Strategy pattern. This immediately informs the granularity because each strategy has a name, and the methods are the things that might vary from one namable strategy to the next.
A typical scenario is a tax calculator. A given invoice has a specific tax treatment depending on the customer or the location. The calculator object is instantiated for the invoice and embodies all the expertise necessary to be responsible for answering tax-related questions such as rate per item, handles special rates, due dates, can list the available rates, and calculates various answers as required.
That is one responsibility: it represents the domain knowledge of a specific tax treatment.
One-method-per-class is a naive rule for SRP that takes a mechanical, not domain-centric view of software and I disregard any such advice. Its a slippery slope from there into service objects and other antipatterns.
Chris's objects in the screencast naturally gravitate to one method, not due to the SRP, but because he's also implementing the command pattern for which the fundamental method is usually #perform. Notwithstanding which, command pattern implementations often have a bunch of other methods because they may also participate in a framework with logging, transactions, progress reporting, undo etc.
NB: for calculators, I'd suggest at most one instantiation parameter, usually a domain entity, and avoid internal state except for that entity object and maybe some value caching, because responses from calculators like should be consistent and nullipotent, or at least idempotent.
Join 27,623+ developers who get early access to new screencasts, articles, guides, updates, and more.