One of the more powerful features of dynamically-typed languages like Smalltalk and Ruby is that of duck typing. As long as an object responds to the right messages for a given context, it can be used within that context no matter what actual type it has.

I’ve started thinking of duck-typed interfaces as “roles” that an object might play in a context. In statically-typed languages, roles often take the form of interfaces that are then implemented by the various objects that can play the role. In dynamically-typed languages, these roles are implicit.

Lately I’ve been experimenting with making roles more explicit in my Smalltalk code. Smalltalk doesn’t have interfaces, and I usually don’t want to play the inheritance card for roles. This is especially true when one object plays multiple roles.

When I want to make a role explicit, I define a class for that role. I then define empty methods for each of the behaviors that role needs to have.

For any object that plays that role, I add a unit test to that object’s test class:

Role Test
PlayerTest>>playsMyRole
self assert: Player new playsRole: MyRole

I’ve added assert:playsRole: as an extension method on TestCase:

assert:playsRole:
TestCase>>assert: anObject playsRole: aClass
aClass selectors do:
[:each |
self assert: (anObject respondsTo: each)
description: ('<1p> doesn''t respond to <2p>' expandMacrosWith: anObject
with: each)]

This test makes sure that the role player implements the entire role. If I later change the role, this test will fail unless I update the player accordingly.

I find that these explicit roles also make it easier to test the context in isolation. I can use a DoubleAgent of the role class in my unit tests without having to choose one of the role players. This is especially handy when all of the role players are complex, expensive, or live in different packages that I don’t want to (or shouldn’t) depend on.

Explicit roles are also handy when I’m writing new code that collaborates with existing (legacy) objects that do too much. I can define a role for the part of the legacy object that I care about, assert that the legacy object plays the role, and test my code against the role and not the legacy object. When I am able to refactor and split the legacy object into pieces, the roles give me a good sense of which pieces are important to the rest of the application.

A nice benefit of decoupling roles from inheritance is that an existing object can play a role if it already implements the right methods. I don’t have to modify the existing object to make it play the role. I can just write the test that asserts that it already does.

The downside to making roles explicit is that I end up writing more code, and it starts to feel like using explicit interfaces in Java or C++. As such, I don’t use them everywhere. I only use them where they make sense. For me, they make sense when:

  • Using inheritance is the wrong choice.

  • There are multiple players of the role.

  • One or more of the role players live in different packages.

  • I want to collaborate with a complex legacy object that has too many responsibilities.

  • The role is an important concept in the domain that deserves to be made explicit.

I’ve only been using explicit roles for a few months, but so far I like the impact they’re having on my code. Give these ideas a try and see what you think.