Better Ways to Implement Single-Method Classes
Tags: python, oop, designpatterns, coding
Published: 2023-07-20 07:46:04

---
title: Better Ways to Implement Single-Method Classes
published: true
date: 2023-07-03 08:22:12 UTC
tags: python,oop,designpatterns,coding
canonical_url: https://cloudnative.ly/better-ways-to-implement-single-method-classes-b737c6c32deb
---

![/imagine a class with a single method rewritten as a higher-order function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gbhw0jlfyj98es1kr2v.png)
_/imagine a class with a single method rewritten as a higher-order function_

Suppose you’re coming to a programming language which allows you to program in multiple paradigms, like Python or TypeScript, but you have come from an object-oriented programming (OOP) background. In that case, you might choose to continue building your software in the OOP way you love. Writing code this way is perfectly reasonable, but you might create unnecessary boilerplate code if you insist on using the class keyword for everything. I want to show how you can rewrite a common type of class differently, reducing the amount of typing you have to do and making the code more straightforward to understand.

# Single Method Classes

The type of class I’m talking about is a single-method class. These classes represent a single action, but they might wrap up some state or dependencies which the method will use when invoked.

Let’s look at some examples:

## Simple Action

The simplest form is a function with no dependencies wrapped in a class. The only reason these classes exist is that the language does not support functions outside of classes. Java introduced the Runnable interface for this exact reason.

```python
class Adder:
    def calculate(self, a: int, b: int) -> int:
        return a + b
```

Here we can move the class out of the function altogether.

```python
def add(a: int, b: int) -> int:
    return a + b
```

You might think you have lost the ability to define an interface that your Adder class implemented so that you supply alternative implementations to consumers — for example, a _Calculator_ interface. You may not be able to create an interface (or a Protocol in the Python world) when you only have a function; however, you can still define an abstraction by creating an alias to a callable type.

```python
Calculator = Callable[[int, int], int]
```

## Dependency Injection

Another example of a class with a single method is one where we use constructor injection to provide dependencies for the method. This enables us to pass only the instance to where we need it without having to pass the dependencies too. Here’s an example of such a class.

```python
class OrderCreator:
    def __init__ (self, order_repository: OrderRepository, email_sender: EmailSender):
        self._order_repository = order_repository,
        self._email_sender = email_sender

    def create_order(self, customer: Customer, items: list[Items]) -> None:
        order_id = OrderID.generate()
        order = Order(order_id, items)
        self._order_repository.save(order)
        confirmation_email = Email(to=customer, subject=f"Thank you for your order ${order_id}")
        self._email_sender.send(confirmation_email)
```

We would then use it like this:

```python
order_creator = OrderCreator(some_order_repository, some_email_sender)

# at another code location
order_creator.create_order(logged_in_customer, cart.get_items())
```

We can create this pattern by creating a constructor function which returns a new closure function with the dependencies baked in. We can again use a type alias to define a meaningful named type.

```python
CreateOrder = Callable[[Customer, list[Items]], None]

def new_order_creator(order_repository: OrderRepository, email_sender: EmailSender) -> CreateOrder:
    def create_order(customer: Customer, items: list[Items]) -> None:
        order_id = OrderID.generate()
        order = Order(order_id, items)
        order_repository.save(order)
        confirmation_email = Email(to=customer, subject=f"We have received your order {order_id}")
        email_sender.send(confirmation_email)
    return create_order
```

We would then use it like this:

```python
create_order = new_order_creator(some_order_repository, some_email_sender)

# at another code location
create_order(logged_in_customer, cart.get_items())
```

## Deferred Dependencies

This pattern is the same as the previous example but used the other way around. In this case, we inject the values in the constructor but provide some dependencies when we invoke the method. Let’s see an example:

```python
class Email:
    def __init__ (self, to: str, subject: str, body: str):
        self._to = to
        self._subject = subject
        self._body = body

    def send(self, sender: EmailSender) -> None:
        sender.send(self._to, self._subject, self._body)
```

We would then use it like this:

```python
email = Email(
      to=customer.email_address,
      subject=f"We have received your order {order_id}",
      body=f"""
      Dear {customer.name}
      Thank you for your order!
      """
  )

# at another code location
email.send(some_email_sender)
```

Because the pattern is the same as the previous one, the classless version follows the same pattern:

```python
SendEmail = Callable[[EmailSender], None]

def create_email(to: str, subject: str, body: str) -> SendEmail:
    def send(sender: EmailSender) -> None:
        sender.send(to, subject, body)
    return send
```

We would then use it like this:

```python
send_email = create_email(
      to=customer.email_address,
      subject=f"We have received your order {order_id}",
      body=f"""
      Dear {customer.name},
      Thank you for your order!
      """
  )

# at another code location
send_email(some_email_sender)
```

# Summary

If you create classes with a single method, you might be better off just creating a function. Using functions might seem like you are not doing OOP, but the model remains the same; an instance wraps a behaviour with a single way to invoke it. Also, you can use type aliases to give these functions named abstractions, which you can use for type signature, descriptiveness and communication. The command classes can be necessary for object-oriented languages because there is no better way to implement them.