A Myriad of Class DefinitionsLet's say we need to implement a Pizza application for a friend. After much white-boarding, we finally decide on a code architecture and a series of fancy design patterns we're gonna use to make our Pizza site come to life! Inevitably, we are going to have several class definitions in our codebase:
class Pizza attr_accessor :cheese, :sauce, :toppings def initialize(cheese, sauce, toppings) @cheese = cheese @sauce = sauce @toppings = toppings end # ... more implementation end
class Soda attr_accessor :type, :is_diet def initialize(type, is_diet) @type = type @is_diet = is_diet end # ... yes, more implementation end
class Topping attr_accessor :name, :cost def initialize(name, cost) @name = name @cost = cost end # ... you get it end
The BoilerplateIt's pretty obvious there are repeated elements of code in each of our classes. It's small, but repetitive nonetheless! Any DRY refactors we can do to reduce the code-smell would be an improvement.
When I'm writing class definitions, I frequently find myself writing two elements:
- getters/setters for public attributes
- a constructor with parameterss for initial attribute values
The DRY way to get getters and setters is to use attr_accessor.
attr_accessor :cheese, :sauce, :toppingsTo set initial attribute values upon construction we use an initialize method with parameters. This is basic Ruby function. Within the initialize method, we explicitly define which attribute gets which parameter.
def initialize(cheese, sauce, toppings) @cheese = cheese @sauce = sauce @toppings = toppings end
Reduce the Repetition with StructWe can get rid of BOTH of these snippets of code by inheriting from a Struct instance! Well, kind of. A Struct generates an instance of Class. The new Class instance will have predefined attributes, along with accessor methods for them. By inheriting from the generated class, we essentially get all of the goodies we want in a single line of code!
Our Pizza class from above now becomes:
class Pizza < Struct.new(:cheese, :sauce, :toppings) # no more initialize, just our implementation! endSubsequently, our entire codebase (however tiny) becomes:
class Pizza < Struct.new(:cheese, :sauce, :toppings) end
class Soda < Struct.new(:type, :is_diet) end
class Topping < Struct.new(:name, :cost) end
Mind. Blown. Anyway, I hope this helps someone as much as it has helped me. Comments or questions? Let me know!