mardi 5 janvier 2016

Factory pattern and SRP

I'm writing my own implementation of the Laravel Service Container to practice some design patterns and later make a private microframework.

The class looks like this right now:

class Container implements ContainerInterface
{
    /**
     * Concrete bindings of contracts.
     *
     * @var array
     */
    protected $bindings = [];

    /**
     * Lists of arguments used for a class instantiation.
     *
     * @var array
     */
    protected $arguments = [];

    /**
     * Container's storage used to store already built or customly setted objects.
     *
     * @var array
     */
    protected $storage = [];


    /**
     * Returns an instance of a service
     *
     * @param $name
     * @return object
     * @throws \ReflectionException
     */
    public function get($name) {
        $className = (isset($this->bindings[$name])) ? $this->bindings[$name] : $name;

        if (isset($this->storage[$className])) {
            return $this->storage[$className];
        }

        return $this->make($className);
    }

    /**
     * Creates an instance of a class
     *
     * @param $className
     * @return object
     * @throws \ReflectionException
     */
    public function make($className) {
        $refObject = new \ReflectionClass($className);
        if (!$refObject->isInstantiable()) {
            throw new \ReflectionException("$className is not instantiable");
        }

        $refConstructor = $refObject->getConstructor();
        $refParameters = ($refConstructor) ? $refConstructor->getParameters() : [];

        $args = [];
        // Iterates over constructor arguments, checks for custom defined parameters 
        // and builds $args array
        foreach ($refParameters as $refParameter) {
            $refClass = $refParameter->getClass();
            $parameterName = $refParameter->name;
            $parameterValue =
                isset($this->arguments[$className][$parameterName]) ? $this->arguments[$className][$parameterName]
                    : (null !== $refClass ? $refClass->name
                        : ($refParameter->isOptional() ? $refParameter->getDefaultValue()
                            : null));

            // Recursively gets needed objects for a class instantiation
            $args[] = ($refClass) ? $this->get($parameterValue)
                                  : $parameterValue;
        }

        $instance = $refObject->newInstanceArgs($args);

        $this->storage[$className] = $instance;

        return $instance;
    }

    /**
     * Sets a concrete implementation of a contract
     *
     * @param $abstract
     * @param $concrete
     */
    public function bind($abstract, $concrete) {
        $this->bindings[$abstract] = $concrete;
    }

    /**
     * Sets arguments used for a class instantiation
     *
     * @param $className
     * @param array $arguments
     */
    public function setArguments($className, array $arguments) {
        $this->arguments[$className] = $arguments;
    }
}

It works fine but I clearly see a violation of SRP in the make() method. So I decided to delegate an object creational logic to a separate class.

A problem that I encountered is that this class will be tightly coupled with a Container class. Because it needs an access to $bindings and $arguments arrays, and the get() method. And even if we pass these parameters to the class, the storage still stays in a container. So basically all architecture is wrong and we need, like, 2 more classes: StorageManager and ClassFactory. Or maybe ClassBuilder? And should ClassFactory be able to build constructor arguments or it needs another class — ArgumentFactory?

What do you think guys?

Aucun commentaire:

Enregistrer un commentaire