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.