jeudi 7 avril 2016

How to write factories using Dependency Injection pattern?

I'm new to DI pattern, thus, here is my question.

Consider the following CarFactory exemple:

interface IEngine {}

class RegularEngine implements IEngine {}

class AdvancedEngine implements IEngine {}

class Car
{
    private $engine;

    public function __construct(IEngine $engine)
    {
        $this->engine = $engine;
    }
}

class CarFactory
{
    public function __invoke(bool $isForVipCustomer = false): Car
    {
        // @todo Here, CarFactory creates different Enginges and a Car,
        // but they should be injected instead?
        $engine = $isForVipCustomer ? new AdvancedEngine : new RegularEngine;
        return new Car($engine);
    }
}

// And here is my composition root:
$carFactory = new CarFactory;
$carForTypicalCustomer = $carFactory();
$carForVipCustomer = $carFactory(true);

Now, as you can see, my factory implementation breaks the DI principle: instead of receiving instances from the invoking code (from Injector, if I use DI container), it creates them by itself. So, I tried to rewrite it so that it doesn't break the DI principle. Here is what I got:

interface ICarSimpleFactoryForCarFactory
{
    public function __invoke(IEngine $engine): Car;
}

interface IEngineSimpleFactoryForCarFactory
{
    public function __invoke(): IEngine;
}

class CarFactory
{
    private $carSimpleFactory;
    private $regularEngineSimpleFactory;
    private $advancedEngineSimpleFactory;

    public function __construct(
        ICarSimpleFactoryForCarFactory $carSimpleFactory,
        IEngineSimpleFactoryForCarFactory $regularEngineSimpleFactory,
        IEngineSimpleFactoryForCarFactory $advancedEngineSimpleFactory
    )
    {
        $this->carSimpleFactory = $carSimpleFactory;
        $this->regularEngineSimpleFactory = $regularEngineSimpleFactory;
        $this->advancedEngineSimpleFactory = $advancedEngineSimpleFactory;
    }

    public function __invoke(bool $isForVipCustomer = false): Car
    {
        $engine = ($isForVipCustomer ? $this->advancedEngineSimpleFactory :
            $this->regularEngineSimpleFactory)();
        return ($this->carSimpleFactory)($engine);
    }
}

// And here is my composition root:
// (sinse I'm now using DI, I need to inject some dependencies here)
$carFactory = new CarFactory(
    new class implements ICarSimpleFactoryForCarFactory {
        public function __invoke(IEngine $engine): Car
        {
            return new Car($engine);
        }
    },
    new class implements IEngineSimpleFactoryForCarFactory {
        public function __invoke(): IEngine
        {
            return new RegularEngine;
        }
    },
    new class implements IEngineSimpleFactoryForCarFactory {
        public function __invoke(): IEngine
        {
            return new AdvancedEngine;
        }
    }
);
$carForTypicalCustomer = $carFactory();
$carForVipCustomer = $carFactory(true);

I had to introduce 2 new interfaces of small factories whose only responsibility is to give the opportunity to the Dependency Injector to inject instances to them and, respectively, to main CarFactory. (If I use DI container.)

It seems to me, that such a solution is overcomplicated and breaks the KISS design principle: just to move the use of “new” operator from my classes to composition root, I had to create an interface and its implementation, only to let the injector do its work?

So, what is the correct way to implement factories, following DI pattern? Or, the solution provided by me is the only correct answer to this question?

Aucun commentaire:

Enregistrer un commentaire