Using the Delegation Pattern with Groovy

Author: Paul King
Published: 2024-01-28 08:08PM


The delegation design pattern is used when one class delegates part of its functionality to one or more helper objects (known as delegates). It is often used as an alternative to the object-oriented inheritance mechanism which allows behavior to be derived from a superclass. When used in this way, it is often termed favoring composition over inheritance.

Case Study

As a case study, let’s consider writing some code to track menu items that you might find at your favorite restaurant or take-away food establishment. We’ll use a MenuItem record to capture the name and price of an item we can order:

record MenuItem(String name, int price) { }

For very simple applications, this might be enough. We can simply use other collection types, like sets, maps or lists, of MenuItem instances. For our first example, we’ll just use a list:

var spanishTapas = [
    new MenuItem('Gambas al ajillo', 8),
    new MenuItem('Tortilla de patatas', 6),
    new MenuItem('Calamares a la romana', 7)
]

assert spanishTapas.size() == 3
assert spanishTapas[0].price == 8
assert spanishTapas[-1].name.startsWith('Calamares')
assert spanishTapas.every{ it.price < 10 }

If we want to build more sophisticated applications, we quickly might find it useful to have a Menu class to embody additional functionality. Let’s look at alternative ways to create such a class.

Using explicit "by-hand" delegation

This is the approach that is often suggested in various design pattern guides which talk about the delegation pattern. We have one (or more) helper objects called delegate(s). For numerous methods in the class we are defining, we simply call through to corresponding methods of the delegate(s).

We’ll declare our delegate helper, in this case a list. We’ll define add, getAt and size methods which do no more than pass on the arguments to the delegate’s identically named method. We’ll also add a findByPrice method which contains a tiny bit of its own business logic, but mostly uses an underlying method from the delegate.

Here is what the class might look like:

class Menu {
    private ArrayList<MenuItem> delegate = []

    boolean add(MenuItem newItem) {
        delegate.add(newItem)
    }

    MenuItem getAt(int index) {
        delegate[index]
    }

    int size() {
        delegate.size()
    }

    MenuItem findByPrice(int price) {
        delegate.find{ it.price == price }
    }
}

Here is how we might use the class:

def vietnamese = new Menu().tap {
    add(new MenuItem('Phở', 7))
    add(new MenuItem('Bánh Mì', 5))
}

assert vietnamese[0].price == 7
assert vietnamese.size() == 2
assert vietnamese.findByPrice(7).name == 'Phở'

This class isn’t too hard to understand, but if we wanted to add more list-like functionality into our Menu class, we’ll see a lot more repeated boilerplate code. As the class gets larger, it also becomes harder to see the intent that we are primarily using the delegation pattern with only a handful of methods (potentially) that might add their own business logic.

At this point, we might question whether moving away from inheritance and to composition/delegation was a good idea. Extending from the ArrayList class, say, would automatically derive many of the methods we may be interested in, and we’d eliminate some of the boilerplate delegation methods. But, if a Menu isn’t really just a list, but has added functionality, trying to force fit it into the List inheritance hierarchy will usually not end well. Moreover, if it turns out we need to delegate to more than one helper object, then the single parent class model offered by inheritance (Groovy follows Java here and only allows a single parent), makes it impossible to use this approach.

Before examining Groovy’s special support for delegation (which overcomes the exploding amount of boilerplate problem), let’s look at Groovy’s support for traits. Traits are a mechanism which does overcome the limitation of inheriting behavior from a single parent superclass.

Using Traits

Groovy traits provide a powerful mechanism for inheriting behavior. Unlike extending from a single superclass, you can implement multiple traits and derive behavior from multiple places. Rules and conventions are in place to overcome the diamond inheritance problem. Traits are a bit like Java’s interfaces with default methods but traits have a few additional features, in particular, we’ll use stateful traits which allow inheriting state, not just behavior.

In our case study, let’s now suppose that we want to enhance our Menu class to also have the concept of a date. We might have different menus on different days of the week, or we might evolve the menu over time with seasonal ingredients, or have special menus for special days of celebration.

We’ll explore two traits, one for list-like behavior, and one for date-like behavior. We’ll examine the list-like behavior first, and for simplicity, we’ll follow a very similar approach to our explicit delegation example above, but add a couple of additional methods.

Here is what our trait might look like:

trait HasList {
    List<MenuItem> listDelegate = []
    boolean add(MenuItem item) { listDelegate.add(item) }
    MenuItem getAt(int index) { listDelegate[index] }
    boolean any(Closure predicate) { listDelegate.any(predicate) }
    boolean contains(MenuItem item) { listDelegate.contains(item) }
}

Next will be our trait for date-like behavior. For simplicity, let’s start with a single isBefore method which lets us check whether the intended date for one menu precedes the date of another menu. This is just an example which maps directly onto one of the methods offered by our LocalDate delegate.

Here is what our trait might look like:

trait HasDate {
    LocalDate dateDelegate
    boolean isBefore(LocalDate other) { dateDelegate.isBefore(other) }
}

Now we can create a class using those two traits:

class Menu implements HasList, HasDate {
    Menu(LocalDate date) { dateDelegate = date }
}

There are several advantages to the explicit delegation example:

  • it becomes clear that we are using the delegation pattern

  • if we wanted to add in some methods, like findByPrice that we used earlier, it becomes clear, that such a method is where additional business logic might be added over-and-above the delegation pattern

  • if we have a need for list-like or date-like behavior in other scenarios, we now have some somewhat general-purpose traits that we can reuse

Here is how we might use our new class:

def italianWednesday = new Menu(LocalDate.of(2024, 1, 24)).tap {
    add(new MenuItem('Spaghetti Bolognese', 10))
    add(new MenuItem('Gnocchi di Patate', 11))
    add(new MenuItem('Tiramisù', 9))
}

def italianThursday = new Menu(LocalDate.of(2024, 1, 25)).tap {
    add(new MenuItem('Fettuccine al Pomodoro', 12))
    add(new MenuItem('Pizza Margherita', 10))
    add(new MenuItem('Pannacotta', 10))
}

assert italianWednesday[0].price == 10
assert !italianWednesday.any{ italianThursday.contains(it) }
assert italianWednesday.isBefore(italianThursday.dateDelegate)

Here we are doing a similar price check to what we have seen earlier, then since we might want to stress variety, we are checking that no menu items from Wednesday and Thursday overlap, then we are checking that the date associated with the first menu precedes the second.

Using dynamic language features

Groovy has several dynamic features which facilitate delegation. The Groovy documentation shows an approach using Groovy’s ExpandoMetaClass. Here, we’ll show an approach that makes use of Groovy’s methodMissing hook:

class Menu {
    private ArrayList<MenuItem> delegate = []

    def findByPrice(int price) {
        delegate.find{ it.price == price }
    }

    def methodMissing(String name, args) {
        delegate."$name"(*args)
    }
}

Here we have one explicit method, findByPrice, which we couldn’t delegate directly. All other method calls are intercepted and directed to our delegate.

Here is how we might use the class:

def frenchBakery = new Menu().tap {
    add(new MenuItem('Croissant', 4))
    add(new MenuItem('Baguette', 5))
}

assert frenchBakery[0].price == 4
assert frenchBakery.size() == 2
assert frenchBakery.findByPrice(4).name == 'Croissant'

In general, we could extend our class to also make use of the propertyMissing method, if we had properties (think getters) that we wanted to also delegate. It is also possible to delegate to multiple delegates but the logic in our methodMissing method would become a little more complex.

A difference of this dynamic approach is that the delegate methods don’t appear in our Menu class file since they are discovered at runtime. This might make our life slightly easier if new versions of the delegate class are used, we’ll automatically delegate to any new methods. Similarly, if we are adding methods to our delegate at runtime, this approach can happily delegate to those methods. It does however have the downside that the delegate methods don’t appear in our Menu class file, which means they won’t appear in our Groovydoc, and we might have less favorable IDE completion depending on how smart our tooling is.

We’ll look at an approach that overcomes those downsides next.

Using the @Delegate transform

Groovy also provides compile-time delegation support via the @Delegate transform. Here we annotate two properties (our delegates) with the @Delegate annotation.

@TupleConstructor(includes='date')
class Menu {
    @Delegate
    final ArrayList<MenuItem> items = []
    @Delegate
    final LocalDate date
}

This automatically adds boilerplate methods similar to what we saw earlier for explicit delegation for every public method in the two classes (about 120 methods in total). Also, any interfaces implemented by our delegates are also automatically added to our Menu class’s implements clause.

Here is how we might use it:

def bistroTuesday = new Menu(LocalDate.of(2024, 1, 16)).tap {
    add(new MenuItem('Tacos', 12))
    add(new MenuItem('Chicken Parma', 15))
}
def bistroFriday = new Menu(LocalDate.of(2024, 1, 19)).tap {
    add(new MenuItem('Chicken Parma', 15))
    add(new MenuItem('Fish & Chips', 12))
}

assert bistroTuesday.any{ bistroFriday.contains(it) }
assert bistroTuesday.isBefore(bistroFriday)
assert bistroTuesday instanceof List
assert bistroFriday instanceof ChronoLocalDate

Here we are checking that at least one item appears on both menus and that the Tuesday menu is before the Friday menu.

If the standard delegation options aren’t what we need, we can customise what code is generated for us by using annotation attributes. For example, if we don’t really need all the list and date methods implemented, we can just delegate to the ones we are interested in by using the includes annotation attribute. This now just brings in the delegation boilerplate code for the methods of interest. In this case, we’ll also want to disable the automatic collection of delegate interfaces, since we no longer will be implementing all the methods listed in the interfaces. We do this using the interfaces annotation attribute.

Here is how we might write our class (just for list-like features):

class Menu {
    @Delegate(includes='add,forEach,get,size', interfaces=false)
    final ArrayList<MenuItem> items = []
}

Here is how we might use it:

def japanese = new Menu().tap {
    add(new MenuItem('Sushi', 8))
    add(new MenuItem('Vegetarian Ramen', 12))
    add(new MenuItem('Vegetable Gyoza', 12))
    add(new MenuItem('Teriyaki Tofu', 12))
}

assert japanese.size() == 4
japanese.forEach{ it.price % 4 == 0 }
assert japanese.get(3).price == 12
assert japanese !instanceof List

We can see that the class does have the methods of interest, since we are using those methods in the example, and also that it doesn’t implement the List interface as the last assertion shows.

Groovy use of the delegation pattern

Groovy also uses the delegation pattern internally in numerous places including Closures. You wouldn’t normally do this in normal code, but you can set and change the delegate of a Closure like this example shows:

var sizeClosure = { size() }
sizeClosure.delegate = 5..6
assert sizeClosure() == 2
sizeClosure.delegate = 'foo'
assert sizeClosure() == 3

While this example may not look all that useful, the technique is fundamental to how builders and other nested Closures operate under the covers.

Here’s another example involving dynamically adding a couple of methods to integers using ExpandoMetaClass:

Integer.metaClass {
    twice { multiply(2) }     // (1)
    thrice { delegate * 3 }   // (2)
}
assert 3.twice() == 2.thrice()
  1. Implicit

  2. Explicit

Conclusion

We have seen how to use the delegation pattern in Groovy by hand, as well as with Groovy’s special runtime and compile-time support.