Pular para o conteúdo principal

Plugin

Descrição​

O Plugin liga classes durante a configuração, e não durante a compilação. A escolha da implementação de uma interface sai do código da aplicação e vai para uma configuração centralizada, lida em tempo de execução.

O problema típico aparece quando o código roda em ambientes diferentes e usa um Separated Interface com implementações distintas para cada um: por exemplo, um gerador de chaves em memória para testes unitários e uma sequência do banco em produção. Normalmente a escolha é feita em métodos de fábrica que verificam variáveis de ambiente. À medida que as fábricas se multiplicam, mudar a configuração de um ambiente exige editar várias delas, recompilar e reimplantar.

Com o Plugin, um único arquivo de configuração associa cada interface à implementação do ambiente, e uma fábrica genérica instancia a classe indicada (por reflexão ou carregamento dinâmico). Trocar a implementação passa a ser uma mudança de configuração, sem recompilar. Os contêineres de injeção de dependência modernos são, em essência, implementações sofisticadas desse padrão.

Problemas que resolve​

  • Condicionais de ambiente (if (env == "test")) espalhadas por várias fábricas.
  • Trocar a configuração de implantação exige alterar código, recompilar e reimplantar.
  • Dificuldade de usar implementações diferentes em testes, desenvolvimento e produção.
  • Dificuldade de estender o sistema com novas implementações sem tocar no código existente.

Quando usar​

  • Quando há um Separated Interface com várias implementações para diferentes ambientes (teste, homologação, produção).
  • Quando as escolhas de implementação estão dispersas em várias fábricas ou condicionais.
  • Quando se quer mudar a configuração sem recompilar ou reimplantar o código.
  • Quando terceiros devem poder adicionar implementações (extensões, drivers, conectores).

Quando evitar​

  • Quando há uma única implementação e nenhum motivo para variar por ambiente.
  • Quando a configuração dinâmica tornaria o comportamento difícil de rastrear e depurar, com erros só em tempo de execução.
  • Quando já existe um contêiner de injeção de dependência no projeto: use-o em vez de construir um mecanismo próprio.

Trade-offs​

GanhoCusto / Risco
Mudar implementação sem recompilarErros de configuração só aparecem em tempo de execução
Configuração centralizada em um só lugarMais difícil seguir o fluxo do código (indireção por reflexão)
Facilita trocar por stubs em testes e ambientesExige uma interface bem definida (Separated Interface)
Permite extensões de terceirosSegurança: carregar classes por configuração exige cuidado com a origem
  • Testabilidade: o ambiente de teste aponta para implementações de teste (por exemplo, Service Stub) apenas pela configuração.
  • Complexidade: moderada, pois depende de reflexão, descoberta de classes ou de um contêiner.

Diagrama​

O Client pede à PluginFactory uma implementação da interface. A fábrica consulta a configuração, instancia a classe indicada e devolve o resultado, sem que o cliente saiba qual é.

Exemplo​

Um gerador de identificadores tem implementação em memória para testes e outra baseada em sequência do banco para produção. A escolha vem de um arquivo de propriedades.

// plugins.properties (teste)
// IdGenerator=com.acme.InMemoryIdGenerator
// plugins.properties (produção)
// IdGenerator=com.acme.DatabaseIdGenerator

public class PluginFactory {
private static final java.util.Properties config = load("plugins.properties");

@SuppressWarnings("unchecked")
public static <T> T getPlugin(Class<T> iface) {
String className = config.getProperty(iface.getSimpleName());
try {
return (T) Class.forName(className).getDeclaredConstructor().newInstance();
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("Plugin inválido para " + iface, e);
}
}

private static java.util.Properties load(String file) { /* ... */ return new java.util.Properties(); }
}

IdGenerator ids = PluginFactory.getPlugin(IdGenerator.class);

Em frameworks, equivalem ao Spring (perfis e configuração de beans), ao ServiceLoader do Java e ao sistema de provedores do .NET.

Padrões relacionados​

  • Separated Interface — fornece a interface cujas implementações o Plugin escolhe.
  • Service Stub — implementação típica selecionada em ambientes de teste.
  • Gateway — gateways costumam ser configurados como plugins por ambiente.
  • Registry — alternativa para localizar serviços, mas por consulta a um objeto conhecido, e não por configuração externa.
  • Factory Method — o Plugin substitui fábricas com condicionais por uma fábrica genérica dirigida por configuração.
  • Abstract Factory — também escolhe uma família de implementações, mas fixada no código.
  • Strategy — o Plugin é uma forma de selecionar a estratégia por configuração.
  • Externalized Configuration — a mesma ideia de tirar a configuração do código, aplicada a serviços.

Referência​