Archive for August, 2011

East example code …

Wednesday, August 10th, 2011

People still ask me about East Oriented code. I hope what follows helps shed some light on the topic of East and immutable objects and how they play nicely with each other. Attached are some files (I hope I can attach them) in Ruby which implements a contrived example.

In my example I have a Customer object which can be initialised with values and after which these values can not be changed = immutable. To print the Customer you send the printOn message passing in the ‘thing’ you want the customer printed on to.

Customer forms a customer writer ‘contract’ between itself and classes that implement the customer writer contract. This contract enables the customer to print onto any object that supports the ‘contract’ - essential any object that responds to the messages the Customer sends from printOn.

To help with the example I have two objects that implement the ‘contract’, CustomerConsoleWriter and CustomerJsonWriter.

The magic is that the Customer doesn’t need to change when you introduce new writers, be they a CustomerDatabaseWriter or  a CustomerHtmlWidgetWriter etc.

You can further extend this magic by having writers that know how to delegate to other writers - for example you could construct a CustomerFileWriter with a CustomerHtmlWidgetWriter to write a Html representation of the Customer to a file.

I have left this combinatory magic to you.

Here is a run using the interractive ruby shell (IRB) showing this magic in action (I have removed output that isnt relevant)

:001 > $:.unshift File.dirname(__FILE__)
:002 > require ‘customer’
:004 > require ‘customer_writer’
:005 > require ‘customer_console_writer’
:006 > require ‘customer_json_writer’
:007 > c = Customer.new(’Ronny’, ‘H’, 33)
 => #<Customer:0×2a7de90 @name=”Ronny”, @surname=”H”, @age=33>
:008 > w1 = CustomerConsoleWriter.new
 => #<CustomerConsoleWriter:0×2a74318>
:009 > w2 = CustomerJsonWriter.new
 => #<CustomerJsonWriter:0×2a33db8>
:010 > c.printOn(w1)
Name: Ronny
Surname: H
Age: 33
 => nil
:011 > c.printOn(w2)
{”Name”=>”Ronny”, “Surname”=>”H”, “Age”=>33}
 => nil

*end* I cut out some of the Ruby IRB text.

So now you see an immutable object go East and provide a mechanism that allows a loose coupling and unlimited possibilities for printing Customers - e: limited by whatever classes you supply.

The internals of the Customer object can change without effecting the contract. Other contracts could be created - like a translation contract or a storage contract (see below).

For testing you can use a Mock with expectations on it to test that a Customer is conforming to the Customer Writer contract and you don’t need any existing writers. Likewise you can test CustomerWriters without having any valid Customer objects.

What objects need the data from Customer - all those that implement the CustomerWriter contract.

What about storing ?  I suggest a storeOn method in Customer that send store message. eg: storeName
The difference would be what the Writers do with that message. Like writing the binary rather than string representation. Id probably separate classes that respond to storeOn from those that understood printOn.

With the dynamics of languages like Ruby or Smalltalk you can make the writers even more magic. Consider the Customers age being a class Age rather than a number - When passed to the writer with printAge() the writer could then send it as_date() and now both a Date and an Age object can be printed. This is more about making writers smarter than East and immutability.

Imagine if you took the approach of using a dynamic factory approach to the writers whereby the Customer could use a factory to lookup the writer given the thing you are writing to - for example:   customer.printOn({})    would take the type / class of the object supplied and dynamically create an instance of the writer to use - in this case the passed objects is a hash, so the factory would find a CustomerHashWriter to write to.    There are a lot of possibilities.

When I think about it the only things I typically do with an object can be categorised as one of the following:

1. print - human representation
2. store - non-human representation
3. read - from non-human representation
4. convert - from one representation to another.

I typically use the following methods corresponding to the categories above:

1. printOn
2. storeOn
3. readFrom
4. convertTo

Creation and destruction are needed too, but these are typically handled by the implementation language.

I have in the past written an entire system using this approach and it was simple to modify and understand.

Here is the Ruby code: East Ruby Example