Um tema recorrente em meu trabalho de desenvolvimento foi o uso ou a criação de uma arquitetura de plug-in interna. Eu já vi várias abordagens - arquivos de configuração (XML, .conf e assim por diante), estruturas de herança, informações de banco de dados, bibliotecas e outros. Em minha experiência:
- Um banco de dados não é um ótimo lugar para armazenar suas informações de configuração, especialmente combinadas com dados
- Tentar isso com uma hierarquia de herança exige que o conhecimento sobre os plug-ins seja codificado, o que significa que a arquitetura do plug-in não é tão dinâmica
- Os arquivos de configuração funcionam bem para fornecer informações simples, mas não podem lidar com comportamentos mais complexos
- As bibliotecas parecem funcionar bem, mas as dependências unidirecionais precisam ser criadas com cuidado.
Como procuro aprender com as várias arquiteturas com as quais trabalhei, também busco sugestões na comunidade. Como você implementou uma arquitetura de plug-in SOLID? Qual foi o seu pior fracasso (ou o pior fracasso que você já viu)? O que você faria se implementasse uma nova arquitetura de plug-in? Qual projeto de código aberto ou SDK com o qual você trabalhou tem o melhor exemplo de uma boa arquitetura?
Alguns exemplos que encontrei por conta própria:
- Módulo do Perl :: Plugable e IOC para injeção de dependência no Perl
- Os vários frameworks Spring (Java, .NET, Python) para injeção de dependência.
- Uma pergunta SO com uma lista para Java (incluindo interfaces de provedor de serviços )
- Uma pergunta SO para C ++ apontando para um artigo do Dr. Dobbs
- Uma pergunta SO referente a uma idéia específica de plug-in para o ASP.NET MVC
Esses exemplos parecem ter vários pontos fortes da linguagem. Uma boa arquitetura de plugins está necessariamente ligada ao idioma? É melhor usar ferramentas para criar uma arquitetura de plug-in ou fazê-lo por conta própria nos seguintes modelos?
Respostas:
Esta não é uma resposta , mas sim um monte de observações / exemplos potencialmente úteis.
Uma maneira eficaz de tornar seu aplicativo extensível é expor seus internos como uma linguagem de script e escrever todas as coisas de nível superior nessa linguagem. Isso o torna bastante modificável e praticamente à prova do futuro (se suas primitivas forem bem escolhidas e implementadas). Uma história de sucesso desse tipo de coisa é o Emacs. Eu prefiro isso ao sistema de plugins do estilo eclipse, porque se eu quiser estender a funcionalidade, não preciso aprender a API e escrever / compilar um plug-in separado. Eu posso escrever um trecho de 3 linhas no próprio buffer atual, avaliá-lo e usá-lo. Curva de aprendizado muito suave e resultados muito agradáveis.
Um aplicativo que estendi um pouco é o Trac . Possui uma arquitetura de componentes que, nessa situação, significa que as tarefas são delegadas a módulos que anunciam pontos de extensão. Você pode implementar outros componentes que se encaixariam nesses pontos e alterar o fluxo. É um pouco como a sugestão de Kalkie acima.
Outro bom é py.test . Segue a filosofia "melhor API não existe API" e depende exclusivamente dos ganchos que são chamados em todos os níveis. Você pode substituir esses ganchos em arquivos / funções nomeados de acordo com uma convenção e alterar o comportamento. Você pode ver a lista de plugins no site para ver com que rapidez / facilidade eles podem ser implementados.
Alguns pontos gerais.
fonte
Na minha experiência, descobri que existem realmente dois tipos de arquiteturas de plug-in.
Eclipse model
que se destina a permitir a liberdade e é aberto.narrow API
porque o plug-in preencherá uma função específica.Para declarar isso de uma maneira diferente, um permite que plug-ins acessem seu aplicativo, enquanto o outro permite que seu aplicativo acesse plug-ins .
A distinção é sutil e, às vezes, não há distinção ... você quer os dois para a sua aplicação.
Não tenho muita experiência com o Eclipse / Abrindo seu aplicativo para o modelo de plug-ins (o artigo na publicação de Kalkie é ótimo). Eu li um pouco sobre o modo como o eclipse faz as coisas, mas nada além disso.
Blog de propriedades de Yegge fala um pouco sobre como o uso do padrão de propriedades permite plugins e extensibilidade.
A maior parte do trabalho que fiz utilizou uma arquitetura de plug-in para permitir que meu aplicativo acesse plug-ins, coisas como hora / exibição / dados de mapa etc.
Anos atrás, eu criava fábricas, gerenciadores de plugins e arquivos de configuração para gerenciar tudo isso e deixava-me determinar qual plugin usar em tempo de execução.
DI framework
faço a maior parte desse trabalho.Ainda preciso escrever adaptadores para usar bibliotecas de terceiros, mas eles geralmente não são tão ruins assim.
fonte
Uma das melhores arquiteturas de plug-in que eu já vi é implementada no Eclipse. Em vez de ter um aplicativo com um modelo de plug-in, tudo é um plug-in. O aplicativo base em si é a estrutura de plug-in.
http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html
fonte
Descreverei uma técnica bastante simples que utilizei no passado. Essa abordagem usa a reflexão em C # para ajudar no processo de carregamento do plug-in. Essa técnica pode ser modificada para que seja aplicável ao C ++, mas você perde a conveniência de poder usar a reflexão.
Uma
IPlugin
interface é usada para identificar classes que implementam plugins. Métodos são adicionados à interface para permitir que o aplicativo se comunique com o plug-in. Por exemplo, oInit
método que o aplicativo usará para instruir o plugin a inicializar.Para encontrar plug-ins, o aplicativo verifica uma pasta de plug-ins para assemblies .Net. Cada montagem é carregada. O Reflection é usado para procurar classes que implementam
IPlugin
. Uma instância de cada classe de plug-in é criada.(Como alternativa, um arquivo Xml pode listar os assemblies e classes a serem carregados. Isso pode ajudar no desempenho, mas nunca encontrei um problema com o desempenho).
O
Init
método é chamado para cada objeto de plug-in. É passada uma referência a um objeto que implementa a interface do aplicativo:IApplication
(ou algo mais específico do seu aplicativo, por exemplo, ITextEditorApplication).IApplication
contém métodos que permitem que o plug-in se comunique com o aplicativo. Por exemplo, se você estiver escrevendo um editor de texto, essa interface terá umOpenDocuments
propriedade que permita aos plugins enumerar a coleção de documentos abertos no momento.Este sistema de plugins pode ser estendido para linguagens de script, por exemplo, Lua, criando uma classe de plugins derivada, por exemplo,
LuaPlugin
que encaminhaIPlugin
funções e a interface do aplicativo para um script Lua.Esta técnica permite-lhe implementar de forma iterativa seus
IPlugin
,IApplication
e outras interfaces específicas do aplicativo durante o desenvolvimento. Quando o aplicativo estiver completo e bem refatorado, é possível documentar suas interfaces expostas e você deve ter um bom sistema para o qual os usuários possam escrever seus próprios plugins.fonte
Geralmente eu uso o MEF. O Managed Extensibility Framework (ou MEF) simplifica a criação de aplicativos extensíveis. O MEF oferece recursos de descoberta e composição que você pode aproveitar para carregar extensões de aplicativos.
Se você estiver interessado leia mais ...
fonte
Certa vez, trabalhei em um projeto que tinha que ser tão flexível na maneira como cada cliente podia configurar o sistema, que o único bom design que encontramos foi enviar ao cliente um compilador C #!
Se a especificação for preenchida com palavras como:
Faça muitas perguntas sobre como você dará suporte ao sistema (e como o suporte será cobrado, pois cada cliente pensará que seu caso é o caso normal e não precisará de plug-ins.), Como na minha experiência
fonte
Na minha experiência, as duas melhores maneiras de criar uma arquitetura de plug-in flexível são as linguagens de script e as bibliotecas. Esses dois conceitos são ortogonais em minha mente; os dois podem ser misturados em qualquer proporção, como na programação funcional e orientada a objetos, mas encontram suas maiores forças quando equilibrados. Uma biblioteca é normalmente responsável por cumprir uma interface específica com funcionalidade dinâmica, enquanto os scripts tendem a enfatizar a funcionalidade com uma interface dinâmica.
Eu descobri que uma arquitetura baseada em scripts gerenciando bibliotecas parece funcionar melhor. A linguagem de script permite a manipulação de alto nível de bibliotecas de nível inferior, e as bibliotecas são liberadas de qualquer interface específica, deixando toda a interação no nível do aplicativo nas mãos mais flexíveis do sistema de script.
Para que isso funcione, o sistema de script deve ter uma API bastante robusta, com ganchos nos dados, na lógica e na GUI do aplicativo, além da funcionalidade básica de importação e execução de código das bibliotecas. Além disso, geralmente é necessário que os scripts sejam seguros, no sentido em que o aplicativo pode se recuperar normalmente de um script mal escrito. Usar um sistema de script como uma camada indireta significa que o aplicativo pode se destacar mais facilmente no caso de Something Bad ™.
O meio de empacotar plugins depende muito da preferência pessoal, mas você nunca pode dar errado com um arquivo compactado com uma interface simples, digamos
PluginName.ext
no diretório raiz.fonte
Eu acho que você precisa primeiro responder à pergunta: "Quais componentes devem ser plugins?" Você deseja manter esse número no mínimo absoluto ou o número de combinações que você deve testar explode. Tente separar seu produto principal (que não deve ter muita flexibilidade) da funcionalidade do plug-in.
Descobri que o diretor do IOC (Inversion of Control) (leia springframework) funciona bem para fornecer uma base flexível, à qual você pode adicionar especialização para simplificar o desenvolvimento de plugins.
fonte
O padrão de plug-in é um padrão de software para estender o comportamento de uma classe com uma interface limpa. Frequentemente, o comportamento das classes é estendido pela herança de classes, onde a classe derivada substitui alguns dos métodos virtuais da classe. Um problema com esta solução é que ela entra em conflito com a ocultação da implementação. Isso também leva a situações em que a classe derivada se torna um local de encontro de extensões de comportamento não relacionadas. Além disso, o script é usado para implementar esse padrão, como mencionado acima "Faça internals como uma linguagem de script e escreva todas as coisas de nível superior nessa linguagem. Isso a torna bastante modificável e praticamente à prova do futuro". Bibliotecas usam scripts para gerenciar bibliotecas. A linguagem de script permite manipulação de alto nível de bibliotecas de nível inferior. (Também como mencionado acima)
fonte