Neste artigo vamos criar uma fabrica de robôs, onde teremos três classes concretas de robôs, uma classe abstrata para os robôs e uma fabrica para instanciarmos os robôs na aplicação, todas as classes concretas deverão ser capazes de sobreviver na aplicação de forma independente da fabrica, o resultado que vamos alcançar com a criação da fabrica é podermos instanciar robôs concretos de forma dinâmica, hora instanciaremos uma classe hora outra, sem termos que alterar a tipo do objeto que esta no teste.
Quando temos um grupo de classes concretas relacionadas teríamos que escrever o código assim:
String tipo = "C6PO";
robos robo;
if (tipo.Equals("C6PO"))
robo = new C6PO();
else if (tipo.Equals("Bumblebee"))
robo = new Bumblebee();
else if (tipo.Equals("Optimus_Prime"))
robo = new Optimus_Prime();
Assim temos varias classes sendo instanciadas, e a decisão de qual instanciar é tomada no tempo de execução. Um código assim no meio da sua aplicação pode gerar problemas na hora de adicionar ou remover robôs, e esse tipo de código sempre termina espalhado no seu aplicativo, sendo assim você teria que procurar e analisar todos os locais que você utilizou esse bloco.
Vamos ver como o factory vai nos ajudar nas questões levantadas.
Vamos começar pelos enumeradores, ainda vai ficar um enumerador que iremos criar quando criarmos a fabrica.
public enum EDirecaoAndar
{
Frente,
Traz,
Esquerda,
Direita
}
public enum EGirar
{
Esquerda,
Direita
}
public enum EModo
{
veiculo,
robo
}
Os nossos robôs terão ações e modo, as ações serão Andar, Girar, Transformar e executar Scripts, e os modos são veículo e robô.
Agora criaremos a classe abstrata que será base de nossos robôs.
public abstract class robos
{
protected String nome { get; set; }
protected EModo Modo { get; set; }
public void andar(EDirecaoAndar Direcao, int passos)
{
Console.WriteLine("O robo {0} está andando para {1} {2} passo(s)", this.nome, Direcao.ToString(), passos);
}
public void Girar(EGirar Movimento, int grau)
{
Console.WriteLine("O robo {0} está girando para {1} {2}º", this.nome, Movimento.ToString(), grau);
}
public abstract void Transformar();
public abstract void Script(int Numero);
}
Os métodos Girar e Andar já são implementados na classe base, e os métodos Transformar e Script são abstrata para obrigar as classes que herdam a classe base implementar estes métodos, teremos em mente as seguintes situações:
O método Transformar vai alterar o modo de veículo para robô e vice e versa;
O método Script vai ser a chamada para ações em lote e/ou novas ações;
Poderíamos ter criado uma interface ao invés de uma classe abstrata, mas eu queria deixar os métodos comum a todos já implementado, assim ganhando produtividade.
Criaremos agora as classes concretas dos robôs cada um com uma característica diferente e todos herdando da classe abstrata “robos”.
public class C6PO : robos
{
public C6PO()
{
this.nome = "C6PO";
this.Modo = EModo.robo;
}
public override void Transformar()
{
Console.WriteLine("{0} não tem opção de se transformar!", this.nome);
//this.Modo = (this.Modo == EModo.veiculo ? EModo.robo : EModo.veiculo);
}
public override void Script(int Numero)
{
this.andar(EDirecaoAndar.Frente, 20);
this.Girar(EGirar.Esquerda, 90);
this.andar(EDirecaoAndar.Frente, 100);
this.Girar(EGirar.Direita, 19);
}
}
public class Optimus_Prime : robos
{
public Optimus_Prime()
{
this.nome = "Optimus_Prime";
this.Modo = EModo.veiculo;
}
public override void Transformar()
{
this.Modo = (this.Modo == EModo.veiculo ? EModo.robo : EModo.veiculo);
Console.WriteLine("{0} em modo {1}", this.nome, this.Modo.ToString());
}
public override void Script(int Numero)
{
switch (Numero)
{
case 1:
if (this.Modo == EModo.veiculo)
this.Transformar();
this.Girar(EGirar.Esquerda, 25);
this.andar(EDirecaoAndar.Esquerda, 2);
this.andar(EDirecaoAndar.Frente, 10);
break;
}
}
}
public class Bumblebee : robos
{
public Bumblebee()
{
this.nome = "Bumblebee";
this.Modo = EModo.veiculo;
}
public override void Transformar()
{
this.Modo = (this.Modo == EModo.veiculo ? EModo.robo : EModo.veiculo);
Console.WriteLine("{0} em modo {1}", this.nome, this.Modo.ToString());
}
public override void Script(int Numero)
{
switch (Numero)
{
case 1:
if (this.Modo == EModo.veiculo)
this.Transformar();
this.Girar(EGirar.Direita, 1);
this.andar(EDirecaoAndar.Esquerda, 2);
this.andar(EDirecaoAndar.Frente, 1);
break;
case 2:
if (this.Modo == EModo.veiculo)
this.Transformar();
this.andar(EDirecaoAndar.Frente, 15);
this.andar(EDirecaoAndar.Direita, 4);
this.Girar(EGirar.Direita, 180);
this.andar(EDirecaoAndar.Frente, 2);
break;
case 3:
if (this.Modo == EModo.veiculo)
this.Transformar();
Console.WriteLine("{0} fez alguns passos de samba.", this.nome);
break;
}
}
}
Criamos as três classes robôs C6PO, Optimus_Prime e Bumblebee, essas implementa os script, implementa ou não o Transformar.
Neste momento temos os robos que podem ser utilizados em nosso aplicativo perfeitamemente de forma direta e convencional, mas a nossa atenção não é essa...
Agora vamos implementar o conceito do padrão factory, bom já temos a classe abstrata robos que será fundamental para nossa fabrica, pois será o tipo retornado da nossa fabrica. Antes de ir para a fabrica vamos criar mais um enumerador para ser utilizado na factory.
public enum ERobos
{
C6PO,
Optimus_Prime,
Bumblebee
}
Temos ai os nomes das nossas classes. Entenda que para cada classe concreta que criarmos terá que criar um item neste enumerado. Esse passo não é fundamental, mas para termos uma melhor manipulação na hora de utilizarmos os factory.
E agora vamos criar a nossa fabrica.
public static class Factory
{
public static robos Criar(ERobos robo)
{
return Criar(robo.ToString());
}
public static robos Criar(String robo)
{
switch (robo)
{
default:
case "C6PO":
return new C6PO();
case "Optimus_Prime":
return new Optimus_Prime();
case "Bumblebee":
return new Bumblebee();
}
}
}
A fabrica é uma classe static com os métodos statics. O método Criar tem uma sobrecarga, podemos passa o nome da classe ou uma opção do enumerador. Essa classe será responsável por instanciar as classes e retornará um objeto do tipo “robos”. Você pode estar pensando, “Esse bloco de código é muito parecido com o primeiro apresentado”, sim, é praticamente o mesmo, a diferença que agora temos uma classe responsável por isso, com isso não vamos espalhar código para decidir qual rodo será instanciado, em case de uma expansão, faremos facilmente a inserção do robô na nossa fabrica, e independentemente de onde estamos utilizando ela teremos um resultado literalmente igual.
Agora testaremos a nossa fabrica.
class teste
{
static void Main(string[] args)
{
List<ERobos> Robos = new List<ERobos> { ERobos.C6PO, ERobos.Bumblebee, ERobos.Optimus_Prime };
robos robo1;
Robos.ForEach(delegate(ERobos R)
{
robo1 = Factory.Criar(R);
robo1.Script(0);
robo1.Transformar();
robo1.Script(1);
robo1.Script(2);
robo1.Script(3);
robo1.Transformar();
Console.WriteLine();
});
Console.ReadKey();
}
}
A essência deste teste será, criaremos uma lista dos enumeradores com um de cada opção, temos também o robo1 do tipo robos que recebera a classe criada pelo factory, depois disso vamos fazer um for e colocar a posição da lista para criar os objetos do tipo, e com isso executar os métodos das classes fornecida pela fabrica.
Chegamos ao fim deste artigo, espero novamente ter conseguido passar a idéia desse conceito. Até a próxima, ahh não esqueçam de fazer o download do exemplo.
Fontes:
FREEMAN, ERIC & FREEMAN, ELISABETH – Use a Cabeça! Padrões de Projetos (Design Patterns), 2ª Edição [http://www.livrariasaraiva.com.br/produto/1995765/]