Pular para o conteúdo principal

Invertendo a direção das dependências

Hoje, mais uma vez, resolvi um problema de projeto virando o jogo de cabeça para baixo.

Pretendo refazer este artigo no futuro com uns diagramas mais legais; por enquanto segue apenas o texto para não ficar esquecido.

Um sistema extensível exibe padrões conhecidos; por toda parte vemos sistemas de "plugins".

Imaginando nosso sistema com "plugins", vemos um elemento Central e diversos elementos Adicionais que podem ser inseridos e removidos do sistema à vontade. O elemento Central é um só e há vários Adicionais ligados ao Central.

Extraindo dessa imagem uma imagem conceitual, vemos o tipo Central em relação com o tipo Adicional, onde Adicional tem multiplicidade zero ou mais.

Partindo daqui para uma imagem comportamental, vemos o que acontece quando inserimos o Adicional. Ou tentamos ver. O que acontece? Quem faz o quê?

Vamos supor que estamos criando tudo isso com um servidor de aplicação J2EE. Nesse ambiente, os módulos, quando iniciados, sofrem (1) injeção de dependências em @EJB etc., (2) chamadas a @PostConstruct.

Acompanhando nossa imagem conceitual, onde Central têm Adicionais, buscamos entender como Central pode fazer alguma coisa a cada vez que um Adicional é inserido e iniciado no sistema de módulos.

Se nós temos um sistema com resolução e injeção automática de depenências, declararíamos que o Central tem dependência nos Adicionais, com o objetivo de causar o sistema de módulos injetar Adicionais no Central sempre que forem iniciados, e o inverso quando forem removidos.

Como acontece isso? O sistema de módulos ativar uma rotina qualquer de Central a cada vez que um Adicional for iniciado -- uma rotina addDependency por exemplo. O oposto aconteceria quando o Adicional é removido. A esperança é que Central receba várias chamadas a addDependency, cada uma das vezes recebendo uma referência ao Adicional que iniciou.

Certo, mas o que acontece quando se faz necessário reiniciar o módulo Central? Todos os módulos Adicionais permanecem iniciados, o que é muito interessante se estes módulos mantém um estado importante; também é um desperdício finalizar e reiniciar módulos à toa. Quando o módulo Central inicia após parar, o sistema de módulos fará o quê? Deve redescobrir a presença de todos os módulos já iniciados? A linguagem usada até agora indica que os Adicionais prévios serão "perdidos", já que addDependency executa apenas quando Adicionais são iniciados.

Se o sistema de módulos concreto usado pelo projeto não fora capaz de fazer isso, qual é a alternativa? Talvez enumerar todos os módulos Adicionais em um arquivo de configuração, atividade que deve ser realizada a cada vez que um Adicional for inserido ou removido do sistema, tal que o módulo Central possa recarregar a configuração e subsequentemente carregar e descarregar Adicionais de acordo.

Isso está ficando um pouco sacal.

Felizmente, para tornar o efeito desejado muitíssimo mais razoável, é preciso apenas inverter a relação de dependência: o Central não depende do Adicional, mas sim o Adicional depende do Central.

Quando um Adicional for iniciado no sistema, este não chamará o Central para fazer algo; o sistema de módulos chamará o próprio Adicional para fazer o que ele acha que deve fazer.


Ao iniciar um Adicional, o sistema de módulos não injetará o Adicional no Central como algum tipo de dependência, mas o contrário: injetará o Central como dependência do Adicional.

Esta relação é simples, já que apenas um Central existe para todos os Adicionais.

Se os Adicionais conhecem o Central através de uma interface útil -- e com certeza deve haver alguma relação entre os módulos, já que todo Adicional deve oferecer um interface útil em contra-partida -- então os Adicionais podem registrar e desregistrar a si mesmos.

Quando o Adicional inicia no sistema, é natural que o sistema cuide de satisfazer suas dependências; se o Central não está lá, o Adicional não pode subir, o que é óbvio; se o Central está lá previamente, deve ser configurado no Adicional. Se o Adicional é reiniciado, o óbvio acontece; se o Central é reiniciado, todos os Adicionais que dependem dele devem ser interrompidos conjuntamente devido a remoção do dependente, o que é razoável, e reiniciados depois quando a dependência se torna satisfeita, o que também é razoável.

O mesmo efeito se obtém no OSGi e em sistema de módulos similares.

É incrível como essa noção é alienígena ao raciocínio do projetista; para o meu raciocínio e o de projetistas conhecidos por mim. É preciso fazer esforço para imaginar um fluxo de trabalho onde o subordinado é quem pilota o gerente, e não o contrário.

Comentários

Postagens mais visitadas deste blog

return void();

É uma pequeneza mas eu gostaria muito que as linguagens da família do C permitissem retornar void quando a função retorna void . Escrever da forma a seguir me aborrece. public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null)      {        log("x was null");        return;      }      bar(x, "default");    } } Eu ficaria mais feliz escrevendo assim: public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null) return log("x was null");      bar(x, "default");     } }

Por que goto é considerado prejudicial?

Recentemente, o Fabiano Vasconcelos abriu uma discussão no Grupo de Usuários de C e C++: De cara eu vi algo aqui um pouco estranho, se que o amigo Márcio me permite comentar: que muitos programadores, inclusive eu (se que posso ser rotulado como programador) foram instruídos com o princípio de NUNCA usar o goto, por ser considerado um mau estilo de programação. Durante a discussão, o Eduardo Vieira puxou um artigo da KernelTrap sobre uma discussão similar ocorrida no grupo de desenvolvimento do Linux, onde Robert Wilken disse o seguinte: In general, if you can structure your code properly, you should never need a goto, and if you don't need a goto you shouldn't use it. It's just "common sense" as I've always been taught. Unless you're intentionally trying to write code that's harder for others to read. É preciso colocar a máxima "goto considered harmful" na perspectiva histórica adequada. Acredito que praticamente todo programador trein...