Skip to content

How to extend final class

Posted on:November 28, 2023 at 10:00 AM

An astronauta carring a laptop

Probrably you saw the issue that you want to change some class behaviour that you don’t have control of that, and you can’t extend this class :(

It usually happen when you are using some third lib and it blocks you.

Note: be cariful with extend classes, if you see the your class gonna have many lavels of inherince then think in another approach.

Why is it final?

I offen see more and more open source projects closing their classes for extention, and It is happen because, it is more complicate to keep BC (Backward Compatibility) Promise

How to solve it?

Ok, So nice, But I still need to change the behaviour of a final class, how can I solve this?

I solve this with Decorator partener!

Let’s assume there is an external lib with a Render class and you want to change the display method implementation.

namespace ExternalLib;

interface RenderInterface
{
    public function display(): string;
}

// ....
namespace ExternalLib;

use ExternalLib\RenderInterface;

final class Render implements RenderInterface
{
    public function __construct(readonly private TemplateInterface $template) {}

    public function display(string $templatePath): string
    {
        return $this->templateEngineA->render($templatePath);
    }
}

// ....
namespace ExternalLib;

use ExternalLib\RenderInterface;

final class UserController
{
    public function __construct(readonly private RenderInterface $render) {}

    public function indexAction(): Response
    {
        $this->render->display('templates/users/index.template');
    }
}
//It is just to make a simple example, but usually it is handled by your Frameworks' Dependency injection.
$template = new Template();
$render = new \ExternalLib\Render($template); 👈 // we goint to change this line to our new render.
$controller = new \ExternalLib\UserController($render);
$controller->indextAction();

Now we are going to decorate Render to CustomRender class

namespace App;

use ExternalLib\RenderInterface;

final class CustomRender implements RenderInterface
{
    public function __construct(
        readonly private RenderInterface $render,
        readonly private CacheInterface $cache,
    ) {}

    public function display(string $templatePath): string
    {
        if($this->cache->has($templatePath)) {
            return $this->cache->get($templatePath);
        }

        $template = $this->render->display($templatePath);

        //We are using $templatePath as cache's key.
        $this->cache->add($templatePath, $template);

        return $template;
    }
}
// New injection
$render = new \ExternalLib\Render($template);
$cache = new CacheA();
$customRender = \App\CustomRender($render, $cache); 👈 // It is our new rander.
$controller = new \ExternalLib\UserController($yourCustomRender); 👈 //Here we are injecting our new render.

After you have created a new decorator you just need to inject your CustomRender to \ExternalLib\UserController, and when indexAction execute $this->render->display($templatePath), it will call your CustomRender.

As you can see you didn’t need to reimplement the whole render just the method that you want and reuse the current code from external lib.

I am using PHP <3, But It works for any OOP language.

Design problems

Sometime you will find Interfaces that forced you implement method that you don’t want to implement. e.g:

namespace ExternalLib;

interface RenderInterface
{
    public function display(): string;

    public function methodA(): int;

    public function methodB(): string;

    //.. sometime more methods.

But even you want to change display behaviour only, you still need to implement others methods in your decorate, and it will be like this.

namespace App;

use ExternalLib\RenderInterface;

class CustomRender implements RenderInterface
{
    public function __construct(readonly private RenderInterface $externalLibRender) {}

    public function display(): string
    {
        // your implementation
    }

    public function methodA(): int
    {
        return $this->externalLibRender->methodA();
    }

    public function methodB(): string
    {
        return $this->externalLibRender->methodB();
    }
}

If you do the example above it still work, it won’t be pretty, But works :D.

But it has a design problem, because an interface shouldn’t force you implement a method.

Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.

I don’t want to be so strict and say that an interface must have one method only, But if it is forcing you implement a lot of method, Then make a suggestion in the lib that is doing it, and see if it could be improved.

Suggestions?

if you have any suggestion or point that I could improve please open a discussion here, I am open to hear your opnion about this.