Padrão de Projeto - Método Fábrica

Método Fábrica

Padrões de "Criação de Objetos"

Os padrões de "criação de objetos" permitem contornar o uso do operador 'new' para evitar o acoplamento rígido (dependência de classes concretas) que ocorre durante a criação de objetos, garentindo assim a estabilidade na criação de objetos. Este é o primeiro passo após a abstração de interfaces.

Padrões Clássicos

Método Fábrica

Método Abstrato

Prototype

Builder

Motivação

Em sistemas de software, frequentemente enfrentamos a necessidade de criar objetos. Devido às mudanças nos requisitos, os tipos concretos dos objetos a serem criados frequentemente mudam.

Como lidar com essa mudança? Como contornar o método convencional de criação de objetos (new) e fornecer um "mecanismo de encapsulamento" para evitar o acoplamento rígido entre o programa cliente e o "trabalho de criação de objetos concretos"?

Definição do Padrão

Define uma interface para a criação de objetos, permitindo que as subclasses decidam qual classe instanciar. O Método Fábrica adia a instanciação de uma classe para as subclasses (objetivo: desacoplamento, meio: função virtual).

Exemplo 1:

#pragma once

#include <string>
using namespace std;

class IDivididor {
public:
    virtual void Dividir() = 0;
    virtual ~IDivididor() {}
};

class BinaryDivididor : public IDivididor{
public:
    BinaryDivididor(string nome_arquivo, int quantidade)
        : nome_arquivo_(nome_arquivo)
        , quantidade_(quantidade) 
    {
        //...
    }

    void Dividir() {
        //...
    }

private:
    string nome_arquivo_;
    int quantidade_;
};

class TextoDivididor : public IDivididor {
public:
    TextoDivididor(string nome_arquivo, int quantidade)
        : nome_arquivo_(nome_arquivo)
        , quantidade_(quantidade)
    {
        //...
    }

    void Dividir() {
        //...
    }

private:
    string nome_arquivo_;
    int quantidade_;
};

class ImagemDivididor : public IDivididor {
public:
    ImagemDivididor(string nome_arquivo, int quantidade)
        : nome_arquivo_(nome_arquivo)
        , quantidade_(quantidade)
    {
        //...
    }

    void Dividir() {
        //...
    }

private:
    string nome_arquivo_;
    int quantidade_;
};

class VideoDivididor {
public:
    VideoDivididor(string nome_arquivo, int quantidade)
        : nome_arquivo_(nome_arquivo)
        , quantidade_(quantidade)
    {
        //...
    }

    void Dividir() {
        //...
    }

private:
    string nome_arquivo_;
    int quantidade_;
};

class CaixaTexto {
public:
    string getTexto() {
        return "XXX";
    }
};
class BarraProgresso {};

class FormularioPrincipal {
    CaixaTexto* txtCaminhoArquivo = nullptr;
    CaixaTexto* txtQuantidadeArquivo = nullptr;
    BarraProgresso* barraProgresso = nullptr;

public:
    void Botao1_Clique() {

        string caminho_arquivo = txtCaminhoArquivo->getTexto();
        int quantidade = atoi(txtQuantidadeArquivo->getTexto().c_str());

        //Dependência de classe concreta
        IDivididor* divididor_arquivo = 
            new BinaryDivididor(caminho_arquivo, quantidade);

        divididor_arquivo->Dividir();
    }
};

Exemplo 2:

#pragma once

#include <string>
using namespace std;

class IDivididor {
public:
    virtual void Dividir() = 0;
    virtual ~IDivididor() {}
};

class BinaryDivididor : public IDivididor {
public:
    BinaryDivididor(string nome_arquivo, int quantidade)
        : nome_arquivo_(nome_arquivo)
        , quantidade_(quantidade)
    {
        //...
    }

    void Dividir() {
        //...
    }

private:
    string nome_arquivo_;
    int quantidade_;
};

class TextoDivididor : public IDivididor {
public:
    TextoDivididor(string nome_arquivo, int quantidade)
        : nome_arquivo_(nome_arquivo)
        , quantidade_(quantidade)
    {
        //...
    }

    void Dividir() {
        //...
    }

private:
    string nome_arquivo_;
    int quantidade_;
};

class ImagemDivididor : public IDivididor {
public:
    ImagemDivididor(string nome_arquivo, int quantidade)
        : nome_arquivo_(nome_arquivo)
        , quantidade_(quantidade)
    {
        //...
    }

    void Dividir() {
        //...
    }

private:
    string nome_arquivo_;
    int quantidade_;
};

class VideoDivididor {
public:
    VideoDivididor(string nome_arquivo, int quantidade)
        : nome_arquivo_(nome_arquivo)
        , quantidade_(quantidade)
    {
        //...
    }

    void Dividir() {
        //...
    }

private:
    string nome_arquivo_;
    int quantidade_;
};

//Classe base fábrica
class FabricaDivididor {
public:
    virtual IDivididor* CriarDivididor(string nome_arquivo, int quantidade) = 0;
    virtual ~FabricaDivididor() {}
};

//Classes de fábrica concretas
class FabricaBinaryDivididor : public FabricaDivididor {
    IDivididor* CriarDivididor(string nome_arquivo, int quantidade) {
        return new BinaryDivididor(nome_arquivo, quantidade);
    }
};

class FabricaTextoDivididor : public FabricaDivididor {
    IDivididor* CriarDivididor(string nome_arquivo, int quantidade) {
        return new TextoDivididor(nome_arquivo, quantidade);
    }
};

//...

class CaixaTexto {
public:
    string getTexto() {
        return "XXX";
    }
};

//O FormularioPrincipal não depende de fábricas ou classes concretas
//O desacoplamento não elimina a dependência de coisas concretas, mas as concentra em um único lugar
class FormularioPrincipal {
    CaixaTexto* txtCaminhoArquivo = nullptr;
    CaixaTexto* txtQuantidadeArquivo = nullptr;
    FabricaDivididor* fabricaDivididor_ = nullptr;

public:

    FormularioPrincipal(FabricaDivididor* fabricaDivididor) 
        : fabricaDivididor_(fabricaDivididor)
    {}

    void Botao1_Clique() {

        string caminho_arquivo = txtCaminhoArquivo->getTexto();
        int quantidade = atoi(txtQuantidadeArquivo->getTexto().c_str());

        IDivididor* divididor_arquivo =
            fabricaDivididor_->CriarDivididor(caminho_arquivo, quantidade);

        divididor_arquivo->Dividir();
    }
};

Pontos-Chave:

O padrão Método Fábrica é utilizado para isolar o acoplamento entre os usuários dos objetos e as classes concretas. Diante de um tipo concreto que frequentemente muda, o acoplamento rígido (new) pode tornar o software frágil.

O padrão Método Fábrica, através de técnicas de programação orientada a objetos, adia o trabalho de criação de objetos concretos para as subclasses, implementando assim uma estratégia de extensão (em vez de modificação) e resolvendo eficazmente esse problema de acoplamento rígido.

O padrão Método Fábrica resolve a mudança de necessidades para "objetos individuais". A desvantagem é que requer que os métodos de criação/parâmetros sejam idênticos.

Tags: design-patterns factory-method object-oriented-programming cpp decoupling

Publicado em 6-7 18:34 por Thomas