The ThingDoer pattern

2022-04-16

I haven't seen this pattern named yet, but I call it "ThingDoer".

It goes like this: take a function which does something.


def do_thing(x: Foo, y: Bar) -> Baz:
    ...

Disguise the function as a class:


class ThingDoer:
    def __init__(self, x: Foo, y: Bar):
        self.x = x
        self.y = y

    def do(self) -> Baz:
        ...

Et voilĂ !

Even in a language like Java, there's a better way to do it. (Use the class as a namespace and stick a function on it as a static method.) But doing it in a language with first-class functions is not only ugly, but needless.

This can be taken further.

Variation 1: splitting into multiple methods

Sometimes you find multiple methods, which work (and communicate) by mutating the internal state, and need to be invoked in the correct order:


thing_doer = ThingDoer(x, y)
thing_doer.do_part1()
thing_doer.do_part2()
z = thing_doer.result()

The functional equivalent would be one or more functions. Doing it with one function is obviously both prettier and safer. (z = do_thing(x, y)). Doing it with multiple functions may be prettier, and should be less error-prone.

Variation 2: adding a custom exception class

If the thing can go wrong, then instead of signaling it in the return value (e.g. Optional[Baz]), the programmer raises a custom exception:


class ThingDoerException(Exception):
    pass

There's nothing wrong with using exceptions to signal errors, especially those which are meant to bubble many levels up before they're caught. In Python, with its EAFP approach, exceptions are common. (Heck, raising StopIteration is part of the built-in iterator protocol.)

But custom exception classes are useful only when the caller wants to distinguish between different classes of exceptions. Are you writing a library which exposes several specific kinds of failure in its public interface? Sure, go ahead! Write custom exception classes. (And make sure to subclass the proper built-in ones when appropriate.) But if the caller cannot sensibly adjust his behaviour depending on the error, there's no sense.

Variation 3: putting it into a separate module

Another variation: putting the ThingDoer in a separate module. Java allows only one public class per file, and Java programmers often see it fit to create a new package and directory for it, too. In Python, this can look like:


$ ls -lR thingdoer/
thingdoer/:
total 4
-rw-r--r-- 1 user user    0 Feb 24 01:23 __init__.py
-rw-r--r-- 1 user user 1234 Feb 24 01:23 thingdoer.py

Much boilerplate for no utility.