In a previous article I showed a way to move display code into a template object to manage missing data. Here's what the basic template code looked like:
class Template def display_address(address) province_and_postal_code = [address.province, address.postal_code].compact.join(' ') province_and_postal_code = nil if province_and_postal_code.empty? city_province_postal_code = [address.city, province_and_postal_code].compact.join(', ') city_province_postal_code = nil if city_province_postal_code.empty? [address.street, city_province_postal_code].compact.join("\n") end end
That's relatively short and easy to read code for displaying the data for an address object.
display_address method contains the entire algorithm for displaying the data and stripping away any missing values. When we needed to add a new format type we created an
HtmlTemplate and it contained duplicated code for the
display_address. That reeks of a future bug where we might need a change to the algorithm and only remember to change one template type.
If we add new attributes to our address, we'd need to change every template so it could handle the new data. Inheritance is an easy solution for managing the way multiple types can handle the change.
And because we specifically allow for data to be missing, we can treat our template object like a partially applied function. Here's what our main template will have...
We'll need methods to set the values to be used for the display data, methods to handle the removal of missing values from the data to be processed, and finally the display method.
To set the data values, we can use
class Template attr_accessor :province, :postal_code, :city, :street def province_and_postal_code value = [province, postal_code].compact.join(' ') if value.empty? nil else value end end def city_province_postal_code value = [city, province_and_postal_code].compact.join(', ') if value.empty? nil else value end end def address_lines [street, city_province_postal_code].compact end def display_address address_lines.join("\n") end end
With that change, our additional template types can inherit from our beginning Template class and change the behavior relevant to the needs of its format:
class HtmlTemplate < Template def display_address address_lines.join("
") end end
Eventually we'll find that the addresses we need to handle might require a secondary bit of information like an apartment number.
class Template # Additional attribute attr_accessor :apartment # Updated collection of information def address_lines [street, apartment, city_province_postal_code].compact end end
The way our Address objects interact with these templates would change, of course, but could allow the Address to make decisions about what may be revealed to the outside world:
class Address def display(template) unless protect_privacy? template.street = street template.apartment = apartment template.postal_code = postal_code end template.city = city template.province = province template.display_address end end
By creating a standard set of template methods, we can treat our objects containing data separately from the objects which display them. What else could we do now that we've made this distiction? For example, what might a template for an IO stream look like?