"Is A" or "Acts As"

Written on 2023-07-06

I voiced my preference for the recent interface default methods RFC, and many people told me I was wrong: an interface is only a contract and shouldn't provide implementations.

They are, of course, right, but only half. Let's talk about object relations.

You can think of the classic class/interface relation as an Is A relation: you can say Item Is A Purchasable Item, or, in technical terms: class Item implements Purchasable.

I don't think there's any disagreement here amongst developers, this is the classic definition of an interface. It allows us to write code that works with all types of purchasables, without worrying which concrete implementation we're dealing with.

function createPayment(Purchasable $purchasable): Payment
{
    $price = $purchasable->getPrice();
    
    // …
}

This is where people who argue against interface default methods stop. If this is the only way you're using interfaces, then yes, you're right: interface default methods aren't necessary.

However, there is another way interfaces are used. And mind you: I'm not saying "there is another way interfaces can be used", no, I'm saying this is happening in modern PHP code, today, in many projects.

Here goes. Interfaces can be used to model an Acts As relation — I have a perfect example, thanks to Larry.

Here's the so called LoggerInterface, part of PSR-3:

interface LoggerInterface
{
    public function emergency(
        string|\Stringable $message, 
        array $context = []
    ): void;

    public function alert(
        string|\Stringable $message, 
        array $context = []
    ): void;

    public function critical(
        string|\Stringable $message, 
        array $context = []
    ): void;

    public function error(
        string|\Stringable $message, 
        array $context = []
    ): void;

    public function warning(
        string|\Stringable $message, 
        array $context = []
    ): void;

    public function notice(
        string|\Stringable $message, 
        array $context = []
    ): void;

    public function info(
        string|\Stringable $message, 
        array $context = []
    ): void;

    public function debug(
        string|\Stringable $message, 
        array $context = []
    ): void;

    public function log(
        $level, 
        string|\Stringable $message, 
        array $context = []
    ): void;
}

As you can see, most methods are essentially shortcuts for the log method: they are convenience methods so that you don't have to manually provide a logging level. It's a great design choice to include in the interface, as it forces all implementations to have better accessibility.

However, let's be honest, no one is ever going to need a logger with a different implementation of debug or info or any of the other shorthands. These methods will always look the same:

public function debug(
    string|\Stringable $message, 
    array $context = []
): void
{
    $this->log(LogLevel::DEBUG, $message, $context);
}

In essence, this LoggerInterface is not only describing an Is A relation — if that were the case we'd only need the log method. No, it also describes an Acts As relation: a concrete logger implementation can Act As as proper logger, including the convenience methods associated with it. FileLogger Acts As LoggerInterface, Monolog Acts As LoggerInterface.

It all boils down to the question: is this a valid way of writing and using interfaces? I would say, yes.

I'd encourage you to take a close look at your own projects, and be totally honest for a moment: are any of your interfaces describing an Acts As relationship? If they do, then you cannot make the case against interface default methods.

Things I wish I knew when I started programming

Things I wish I knew when I started programming cover image

This is my newest book aimed at programmers of any skill level. This book isn't about patterns, principles, or best practices; there's actually barely any code in it. It's about the many things I've learned along the way being a professional programmer, and about the many, many mistakes I made along that way as well. It's what I wish someone would have told me years ago, and I hope it might inspire you.

Read more

Comments

Loading…
No comments yet, be the first!
Noticed a tpyo? You can submit a PR to fix it.
HomeRSSNewsletterDiscord© 2025 stitcher.io