Imagina a seguinte interface:
public interface Fabrica {
MeuObjeto createObject();
}
Você tem uma implementação dela assim:
public void FabricaImpl implements Fabrica {
public MeuObjeto createObject() {
MeuObjeto.loadFromDB();
}
}
Legal, não? Agora vamos supor que você precisasse gerar um log todas as vezes que esse método fosse chamado. Mas não sempre, só de vez enquanto. Aliás, só quando você (programador) fosse avaliar o seu programa. Isso não deveria ir para cliente. Como organizar a classe para isso?
Você poderia fazer um proxy. Ele implementa a interface e delega os métodos para classe principal. O proxy é possível pois ele implementa a mesma interface.
public void FabricaProxyComLog implements Fabrica {
private Fabrica fabrica;
public FabricaProxyComLog(Fabrica umaFabrica) {
this.fabrica = umaFabrica;
}
public MeuObjeto createObject() {
logar(); //Fazemos o log
return fabrica.createObject(); //Agora sim, passamos para a fábrica de verdade
}
public void logar() {
//Loga isso num arquivo qualquer.
}
}
Agora, no seu código, bastaria ir na declaração da sua classe de fábrica e trocar isso:
Fabrica f = new FabricaImpl();
Por isso:
Fabrica f = new FabricaProxyComLog(new FabricaImpl());
Reconhece esse padrão? Se você já usou a Collections framework, já deve ter visto que é possível fazer:
List<Integer> lista = new Collections.unmodifiableList(umaListaQualquer);
O que o new Collections.unmodifiableList faz? Ele encapsula a sua lista num proxy, que lança exceção sempre que tentarem modifica-la. Ele não cria uma cópia de sua lista. A mesma coisa para o Collections.synchronizedList.
Se já usou os buffers, deve ter entrado em contato com um tipo especial de proxy, chamado
Decorator. Você provavelmente tinha um InputStream bruto (como o FileInputStream). Daí, vc precisou coloca-lo num buffer para aumentar a performance. O que você fez?
BufferedInputStream bis = new BufferedInputStream(algumFileInputStream);
E se você precisasse ler objetos lá de dentro?
ObjectInputStream ois = new ObjectOutputStream(bis);
Cada decorator adicionou uma pequena funcionalidade na classe original. Sacou?
Esse é o poder que você ganha com proxies.
E agora, o que é um mock? Um mock é uma classe que finge ter uma funcionalidade. É muito usado em JUnits. A grande vantagem do Mock é que vc programa ele para se comportar como você quiser.
Por exemplo, vamos supor que você esteja escrevendo um JUnit para a classe MeuObjeto. Mas a única fábrica do seu programa real é uma que pega lá do Hibernate, num servidor remoto, que está em algum lugar da web. Acho que você não gostaria de configurar um ambiente pesado como esse só para testar a classe meu objeto. Então, o que você faz? R: Um mock.
public class FabricaMock implements Fabrica {
public MeuObjeto createObject() {
return new MeuObjeto(); //Cria um objeto padrão qualquer.
}
}
Agora você poderia implementar um JUnit facilmente:
public class Teste {
@Test
public void testaToString() {
Fabrica f = new FabricaMock(); //Sem o stress de hibernate
MeuObjeto mo = f.createObject();
Assert.assertEquals("default", meuObjeto.toString());
}
}
O legal é que você poderia programar o seu Mock para gerar casos de erro. Por exemplo, para lançar a exceção de que o objeto não existe. E ver como outras classes no seu programa respondem a isso.
Por exemplo:
public class TestaIndexador {
@Test
public void teste() {
//A classe indexador criaria indices de meus objetos
FabricaMock fabrica = new FabricaMock()
fabrica.setFingirNaoTerObjetoNenhum(true);
Indexador indexador = new Indexador(fabrica);
indexador.indexar(); //Não deve dar erro nenhum, se der é problema do indexador!
}
@Test(expected=UnableToIndexException.class)
public void teste2() {
//agora vamos testar se o indexador gera um erro caso a fábrica também gere
FabricaMock fabrica = new FabricaMock()
fabrica.setGerarObjetosNaoIndexaveis(true);
Indexador indexador = new Indexador(fabrica);
indexador.indexar(); //Deve gerar uma UnableToIndexException
}
}
Note que esses métodos setFingirNaoTerObjetoNenhum e setGerarObjetosNaoIndexaveis só existem no Mock. Foram feitos especialmente para testar o Indexador e classes similares, que venham a usar a fábrica. Ele permite que coloquemos situações especiais da fábrica e testemos como os objetos (nesse caso o Indexador) devem se comportar nessas situações.
O que eu sugiro? Pense em ter uma classe concreta, sem métodos estáticos.
Agora, se o seu programa é muito simples, só para faculdade, então permaneça com os statics, pq são mais simples de implementar.