I think this is one of the most incredible inventions on earth.

I’m not talking about the UK electrical outlet specifically, but the electrical outlet in general. Think about how our lives would be without electrical outlets, with just cables ready to be used, like this:

We would need to wire up all the appliances in our homes manually, from cable to cable. Apart from being extremely dangerous, it would be a slow and cumbersome process. Imagine needing to temporarily unwire a lamp to connect a vacuum cleaner. It would take loads of time! Also, you could get the wires all mixed up: connect the ground to the live and the live to the neutral. All of our devices would be coupled together to the point of being too hard to change.
The convenience of the electrical outlet is that it removes all that complexity. My appliances only need to implement an interface that conforms to the outlet (the plug) to be correctly connected and easily swappable to any outlet in my house. It removes the need for me to have low-level knowledge about electricity and wiring. It’s just so much simpler.
Outlet Mentality
When building software, we should be thinking like the inventor of the electrical outlet at all times. But many times we don’t think like that. For instance, if our program logic requires us to write a report, we immediately write it to the filesystem. We do something like this:
<?php
class ReportWriter{ public function writeReport(array $records): void { $resource = fopen('/some/file.path', 'wb');
foreach ($records as $i => $record) { $line = sprintf('Record %d: %s', $i, $record['contents']); fwrite($resource, $line); }
fwrite($resource, PHP_EOL); $end = sprintf('Number of records: %d', count($records)); fwrite($resource, $end); }}The code above is like connecting the appliances in your house (your business logic) directly to the electrical wiring (the filesystem). It’s dangerous and hard to change. What if we want to write the report to a database, or send it over HTTP, or just keep it in memory for testing? We would need to change the entire ReportWriter class.
Instead, we should create an interface:
<?php
interface Writer{ public function write(string $data): int;}
// The filesystem implementing the abstractionclass PhpResource implements Writer{ public static function open(string $filename): PhpResource { return new self(fopen($filename, 'wb')); }
private function __construct( private $resource ) { }
public function write(string $data): int { return fwrite($this->resource, $data); }}
// The business logic using the abstractionclass ReportWriter{ public function __construct( private readonly Writer $output, ) { }
public function writeReport(array $records): void { foreach ($records as $i => $record) { $line = sprintf('Record %d: %s', $i, $record['contents']); $this->output->write($line.PHP_EOL); }
$this->output->write(PHP_EOL); $end = sprintf('Number of records: %d', count($records)); $this->output->write($end.PHP_EOL); }}
// This is how you bootstrap it$resource = PhpResource::open('/some/file.path');$reportWriter = new ReportWriter($resource);Now the high-level business logic doesn’t need to know about the filesystem. The low-level stuff (writing to a file) has been simplified under the Writer interface. The Writer interface is our electrical outlet: it makes it possible to connect our business logic to the filesystem without them knowing anything about each other.
This decoupling is powerful. It makes programs resilient and easy to test. Because we don’t depend on the filesystem now, while testing, we can have an in-memory Writer where we can assert that the contents were written as intended.
This is how you decouple software components: by putting an interface between them. Interfaces, like the electrical outlet, are one of the best inventions since Object Oriented Programming itself.
Conclusion
This is just a summary of the Dependency Inversion Principle in SOLID, which states that “High-level modules should not depend on low-level modules, but rather both should depend on abstractions”. In our example above, high-level business logic (the writing of a report) was depending on low-level filesystem operations (for writing a file). Introducing an abstraction that both parties rely on makes it possible to decouple them. Now our business logic can be freely used with any type that implements Writer.
Generally speaking, making your business code rely on abstractions (interfaces) is the best way to decouple it from other things. This is how I write my programs nowadays: I don’t even start with the database or the HTTP framework, but rather, I design commands and handlers that rely only on interfaces to do the business actions I need, and then I implement them later. This ensures I focus on the things my business actions need. This approach has the nice benefit that it completely decouples your code from a framework or database library, making it more robust, easier to test, and easier to change.
Just remember: interfacing is decoupling.