Although there are many tactics a developer can employ to limit the influence of these “god objects”, one of the simplest is just reassigning some of their responsibility, and using a delegation pattern may be the simplest way to do just that.

Delegation by Default

The act of delegation in Object Oriented Programming is so common we oftentimes don’t even realize when it’s happening. Take “inheritance” for example: when you create a subclass, you inherit all the parent class’s public methods, and unless those methods are overwritten, any calls to the inherited methods are passed to the parent class.

Even in methods which are overwritten, specifically calling the superclass’s method through the use of super, is delegation, because the child class is relying on the parent to accomplish what it doesn’t need to.

Here’s an example of what I mean:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

classReport

def records

# return a list of report records

end

def print

# output the report

end

end

classInTriplicateReport<Report

def print

super

super

super

end

end

report=InTriplicateReport.new

report.records# => returns the records in the report

report.print# => Prints the report in triplicate

Here we see that InTriplicateReport inherits both records and print from the Report class. InTriplicateReport doesn’t overwrite records, but instead relies on the parent to handle the responsibility. On the other hand, print is overwritten, but we still rely on the parent through our multiple calls to super. In each case, InTriplicateReport delegates responsibility to the parent Report class rather than duplicating effort and code. Later, as requirements change, records or print can be further modified, but until then, it’s best to allow Report to handle the logic.

Explicit Delegation

So inheritance is delegation, but really any time you use another object to perform logic instead of keeping it within the class itself, you are delegating.

In the Report class below, the #print method accepts an object which will handle transforming the report data into a specified format (XML, CSV, and JSON). The Report class doesn’t “care” about the final output, it just wants to output it, and so it delegates the actual formatting to one of the formatter objects.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

classReport

def records

# lists all the reports records

end

def print(delegate)

# logic to handle output

delegate.generate(self.records)

# closing logic

end

end

classCSVFormatter

def generate(records)

# formats to CSV

end

end

classXMLFormatter

def generate(records)

# formats to XML

end

end

classJSONFormatter

def generate(records)

# formats to JSON

end

end

report=Report.new

report.print(CSVFormatter.new)# outputs to CSV

report.print(XMLFormatter.new)# outputs to XML

report.print(JSONFormatter.new)# outputs to JSON

The formatter objects being passed in are the delegates; they handle converting the data to the desired format, while Report maintains focus on outputting the formatted data. By delegating responsibility in this instance, we keep our objects focused on performing a single function (see: Single Responsibility Principle), decouple our logic, and generally make life easier for ourselves.

This style of delegation is commonly used in statically typed languages such as .Net, Java, and Objective-C. In those languages, the delegate must conform to a sort of contract called an “Interface” or “Protocol”. Thankfully, Ruby doesn’t have such hang-ups and assumes that if the object walks like a duck and quacks like a duck, it’s a duck.

Delegation with method_missing

For this example, let’s assume we’re working on an e-commerce system. This system will have “orders” which, in turn, can have zero or more products and will relate to each of those products through a line item. Let’s further assume that we would like to access product information, such as “sku”, “name”, “description”, and “price”, directly from the line_item object rather than chaining out to the product instance (e.g. line_item.product_sku instead of line_item.product.sku).

We can do this using Ruby’s method_missing, a method inherited from BasicObject, and which catches messages sent to an object which are not explicitly defined.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

classOrder

def line_items

# collection of LineItems

end

end

classLineItem

attr_reader:product

def initialize(product)

@product=product

end

def method_missing(method,*args)

ifmethod.to_s.match(/product_(.+)/)

self.product.send($1,*args)

else

super

end

end

end

classProduct

attr_accessor:sku,:name,:description,:price

# product related methods

end

In our example, method_missing looks to see if the message passed begins with “product_”, if it does, it passes that message (sans “product_”) on to the product instance. If the message doesn’t match the pattern, it passes it on up the call chain with super.

Now, if we were to list the line items from an order we could do something like this:

Although it looks as if we are calling specific methods on the line_item object, in reality we’re delegating the messages to the product object.

Delegation with Ruby’s Forwardable

In the above example, we used method_missing to delegate all methods beginning with “product_” not defined in LineItem to the Product class. The advantage here is that it handles all current and future Product methods. If a new method, such as serial_number, is added, LineItem can immediately begin using it without any alterations to the code.

On the other hand, if you need to be more specific about which methods are allowed to be delegated, Ruby’s Forwardable module is a better solution.

Here’s the same LineItem class, rewritten using the Forwardable module.

1

2

3

4

5

6

7

8

9

10

11

12

13

include Forwardable

classLineItem

extend Forwardable

def_delegators:@product,:sku,:name,:description,:price

attr_reader:product

def initialize(product)

@product=product

end

end

In the above code, we extend Forwardable into LineItem, add the def_delegators line, and remove method_missing altogether.

The def_delegators line is pretty straightforward: it states that any call to sku, name, description, and price, should be instead handled by the @product instance variable (note: we could have written :product since we’ve defined it with attr_reader)

Where previously we prepended “product_” to delegated methods, now we can call the method directly, because there’s no longer a need to distinguish product specific methods for method_missing.

Here’s our output rewritten to take advantage of the refactoring:

1

2

3

4

5

puts"SKU,Name,Price"

order.line_items.eachdo|line_item|

puts"%s,%s,%s"%[line_item.sku,line_item.name,line_item.price]

end

If you wanted to keep the “product_” prepended to the method calls – maybe to keep from overwriting another method of the same name – you would need to use def_delegator instead. However, doing so will limit you to defining one delegator at a time.

1

2

3

4

5

6

7

8

9

10

11

12

classLineItem

extend Forwardable

attr_reader:id

# avoid overwriting LineItem#id

def_delegator:@product,:id,:product_id

def_delegators:@product,:sku,:name,:description,:price

...

end

In Object Oriented Programming, delegation is an incredibly useful pattern, and – as is so often the case in Ruby – there are numerous ways to implement it. It is not some elusive concept which is difficult to nail down, but one which we encounter regularly through typical OO development.