Imaster

Factory deve criar, não reter

Em uma recente conversa no Reddit, alguns de nós tomamos uma tangente no assunto sobre o padrão de projetos de software conhecido como factory. Mantive então, e ainda mantenho agora, a ideia de que um “factory” sempre e somente retorna uma nova instância. Se você tem um “factory” que retorna algo diferente de uma nova instância, não é um factory sozinho. No caso de métodos do factory, é um factory + um assessor; no caso de objetos do factory, é um factory + registro.

Um “factory” (seja um método ou um objeto dele) é uma maneira de separar a criação do objeto do uso do objeto.

Vamos dizer que nós precisamos criar um objeto para fazer algum trabalho em um método de classe:

class Example
{
    public function __construct($db)
    {
        $this->db = $db;
    }

    public function doSomething($itemId)
    {
        $data = $this->db->fetchOne(
            "SELECT * FROM items WHERE id = :id",
            ['id' => $itemId]
        );
        $item = new Item($data);

        // do some other work with the item,
        // the return the results of that work.
        return $results;
    }
}

A criação do item é misturada com o uso do produto. O que nós queremos fazer é separar a criação do uso. Uma maneira de fazer isso é usar um método do factory, ou um objeto dele, para lidar com a criação do objeto para nós. Aqui está um exemplo de um método do factory:

class Example
{
    public function __construct($db)
    {
        $this->db = $db;
    }

    public function doSomething($itemId)
    {
        $data = $this->db->fetchOne(
            "SELECT * FROM items WHERE id = ?",
            ['id' => $id]
        );
        $item = $this->item($itemId);

        // do some other work with the item,
        // the return the results of that work.
        return $results;
    }

    public function item($data) // factory method
    {
        return new Item($data);
    }
}

Aqui está um exemplo de um objeto factory injetado:

class Example
{
    public function __construct($db, $factory)
    {
        $this->db = $db;
        $this->factory = $factory;
    }

    public function doSomethingWithItem($id)
    {
        $data = $this->db->fetchOne(
            "SELECT * FROM items WHERE id = ?",
            ['id' => $id]
        );
        $item = $this->factory->item($data);

        // do some other work with the item,
        // the return the results of that work.
        return $results;
    }
}

class Factory // factory object
{
    public function item($data) // factory method
    {
        return new Item($data);
    }
}

Havia algumas pessoas na lista de discussão do Reddit que acreditam que um factory pode ainda reter a instância criada para reutilização. Por exemplo, vamos dizer que queremos criar e reutilizar um objeto colaborador de algum tipo:

class Example
{
    public function doSomething()
    {
        $collab = $this->collab();
        // do stuff, then:
        return $result;
    }

    protected function collab() // factory method?
    {
        if (! $this->collab) {
            $this->collab = new Collab();
        }
        return $this->collab;
    }
}

Até onde eu posso dizer, isso não é apenas um factory. Há três coisas acontecendo lá: inicializar uma propriedade, criar um objeto e acessar uma propriedade. Para dividir as preocupações, seriam necessários dois métodos:

class Example
{
    public function doSomething()
    {
        $collab = $this->getCollab();
        // do stuff, then:
        return $result;
    }

    protected function getCollab() // initialize + access
    {
        if (! $this->collab) {
            $this->collab = $this->newCollab();
        }
        return $this->collab;
    }

    protected function newCollab() // factory
    {
        return new Collab();
    }
}

Agora, a preocupação de criar o objeto é representada pelo método newCollab(), e as preocupações de inicialização e acesso da propriedade são representadas pelo método getCollab(). Na verdade, dividir um método do factory nesse tipo de situação é a recomendação exata do livro GOF Design Patterns na página 113; esse é o livro que primeiro definiu o termo “método de factory”, por isso parece autoritário sobre esse ponto.

O que acontece quando você adiciona retenção para o que seria um objeto do factory? Por exemplo, o seguinte objeto é apenas um método do factory, ou ele faz mais do que apenas criar e retornar novas instâncias?

class WhatIsThatThing
{
    public function getService()
    {
        if (! $this->service) {
            $this->service = $this->newService();
        }
        return $this->service;
    }

    public function newService()
    {
        return new Service();
    }
}

Ele tanto cria como mantém os objetos criados. Isso faz com que ele seja mais do que apenas um factory; e mais parece um container nesse momento.

Finalmente, em relação aos nomes dos métodos, parece que o melhor é indicar o que esperar como resultado de retorno. O padrão é newInstance() em objetos do factory que lidam com apenas uma única classe, e new<Type>() quando o objeto do factory cria muitas classes diferentes. Usar get<Type>() indica que uma instância pré-existente (provavelmente memorizada em uma propriedade) está sendo devolvida.

***

Paul M. Jones faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: http://paul-m-jones.com/archives/6161

Mensagem do anunciante:

Infraestrutura financeira descomplicada é com a iugu! Tão simples que parece brincadeira! Clique aqui e dê um up nos negócios!

Powered by WPeMatico