Come creare un'architettura plug-in flessibile?


154

Un tema ricorrente nel mio lavoro di sviluppo è stato l'uso o la creazione di un'architettura plug-in interna. Ho visto che si è avvicinato in molti modi: file di configurazione (XML, .conf e così via), framework di ereditarietà, informazioni sul database, librerie e altro. Nella mia esperienza:

  • Un database non è il luogo ideale per archiviare le informazioni di configurazione, in particolare combinate con i dati
  • Tentare questo con una gerarchia di ereditarietà richiede la conoscenza dei plug-in da codificare, il che significa che l'architettura dei plug-in non è poi così dinamica
  • I file di configurazione funzionano bene per fornire informazioni semplici, ma non sono in grado di gestire comportamenti più complessi
  • Le biblioteche sembrano funzionare bene, ma le dipendenze a senso unico devono essere create con cura.

Mentre cerco di imparare dalle varie architetture con cui ho lavorato, cerco anche suggerimenti per la comunità. Come hai implementato un'architettura plug-in SOLID? Qual è stato il tuo peggior fallimento (o il peggior fallimento che hai visto)? Cosa faresti se avessi implementato una nuova architettura plug-in? Quale SDK o progetto open source con cui hai lavorato ha il miglior esempio di buona architettura?

Alcuni esempi che ho trovato da solo:

Questi esempi sembrano giocare a vari punti di forza linguistici. Una buona architettura di plugin è necessariamente legata alla lingua? È meglio usare gli strumenti per creare un'architettura plug-in o per farlo sui propri modelli seguenti?


Puoi dire perché un'architettura di plugin è stata un tema comune? Quali problemi risolve o obiettivi a cui si rivolge? Molti sistemi / applicazioni estensibili utilizzano plug-in di qualche forma, ma la forma varia considerevolmente a seconda del problema da risolvere.
mdma,

@mdma - questa è un'ottima domanda. Direi che ci sono alcuni obiettivi comuni in un sistema estensibile. Forse gli obiettivi sono tutto ciò che è comune e la soluzione migliore varia a seconda di quanto estensibile deve essere il sistema, in quale lingua è scritto il sistema e così via. Tuttavia, vedo schemi come il CIO applicati in molte lingue, e mi è stato chiesto di fare plugin simili (per far cadere pezzi che rispondono alle stesse richieste di funzionalità) più e più volte. Penso che sia buono avere un'idea generale delle migliori pratiche per vari tipi di plugin.
solo

Risposte:


89

Questa non è una risposta tanto quanto un mucchio di osservazioni / esempi potenzialmente utili.

  • Un modo efficace per rendere estensibile la tua applicazione è quello di esporre i suoi interni come linguaggio di scripting e scrivere tutte le cose di alto livello in quella lingua. Questo lo rende abbastanza modificabile e praticamente a prova di futuro (se i tuoi primitivi sono ben scelti e implementati). Una storia di successo di questo genere di cose è Emacs. Preferisco questo al sistema di plugin in stile eclissi perché se voglio estendere la funzionalità, non devo imparare l'API e scrivere / compilare un plugin separato. Posso scrivere uno snippet di 3 righe nel buffer corrente stesso, valutarlo e usarlo. Curva di apprendimento molto regolare e risultati molto piacevoli.

  • Un'applicazione che ho esteso un po 'è Trac . Ha un'architettura componente che in questa situazione significa che le attività sono delegate ai moduli che pubblicizzano punti di estensione. È quindi possibile implementare altri componenti che si adatterebbero in questi punti e modificare il flusso. È un po 'come il suggerimento di Kalkie sopra.

  • Un altro buono è py.test . Segue la filosofia "la migliore API non è un'API" e si basa esclusivamente sugli hook chiamati a tutti i livelli. È possibile ignorare questi hook in file / funzioni denominati in base a una convenzione e modificare il comportamento. Puoi vedere l'elenco dei plug-in sul sito per vedere quanto velocemente / facilmente possono essere implementati.

Alcuni punti generali.

  • Cerca di mantenere il tuo core non estensibile / non modificabile dall'utente il più piccolo possibile. Delega tutto ciò che puoi a un livello superiore in modo da aumentare l'estensione. Meno roba da correggere nel nucleo quindi in caso di cattive scelte.
  • Relativo al punto precedente è che non dovresti prendere troppe decisioni sulla direzione del tuo progetto all'inizio. Implementa il sottoinsieme più piccolo necessario e quindi inizia a scrivere plugin.
  • Se stai incorporando un linguaggio di scripting, assicurati che sia un linguaggio completo in cui puoi scrivere programmi generali e non un linguaggio giocattolo solo per la tua applicazione.
  • Ridurre il più possibile la piastra della caldaia. Non preoccuparti di sottoclassi, API complesse, registrazione dei plugin e cose del genere. Cerca di mantenerlo semplice in modo che sia facile e non solo possibile estenderlo. Ciò consentirà di utilizzare maggiormente l'API del plug-in e incoraggerà gli utenti finali a scrivere plug-in. Non solo sviluppatori di plug-in. py.test lo fa bene. Eclipse, per quanto ne so, no .

1
Vorrei estendere il punto del linguaggio di scripting a cui vale la pena considerare di incorporare un linguaggio esistente come Python o Perl
Rudi,

Vero Rudi. Molte lingue sono state progettate per essere integrate. Guile per esempio, è fatto solo per l'incorporamento. Ciò consente di risparmiare tempo se stai rendendo la tua app gestibile tramite script. Puoi semplicemente rilasciare una di queste lingue.
Noufal Ibrahim,

24

Nella mia esperienza ho scoperto che esistono davvero due tipi di architetture plug-in.

  1. Uno segue ciò Eclipse modelche ha lo scopo di consentire la libertà ed è a tempo indeterminato.
  2. L'altro di solito richiede che i plugin seguano un narrow APIperché il plugin riempirà una funzione specifica.

Per dirlo in modo diverso, uno consente ai plug-in di accedere all'applicazione, mentre l'altro consente all'applicazione di accedere ai plug-in .

La distinzione è sottile, e a volte non c'è distinzione ... vuoi entrambi per la tua applicazione.

Non ho molta esperienza con Eclipse / Aprire la tua app al modello di plugin (l'articolo nel post di Kalkie è fantastico). Ho letto un po 'sul modo in cui l'eclissi fa le cose, ma niente di più.

Il blog delle proprietà di Yegge parla un po 'di come l'uso del modello delle proprietà consenta plugin ed estensibilità.

La maggior parte del lavoro che ho svolto ha utilizzato un'architettura di plug-in per consentire alla mia app di accedere a plug-in, ad esempio dati relativi a tempo / visualizzazione / mappa, ecc.

Anni fa avrei creato fabbriche, gestori di plugin e file di configurazione per gestirli tutti e permettermi di determinare quale plugin usare in fase di esecuzione.

  • Ora di solito mi occupo solo DI frameworkdella maggior parte di quel lavoro.

Devo ancora scrivere adattatori per utilizzare librerie di terze parti, ma di solito non sono poi così male.


@Justin sì, DI = iniezione di dipendenza.
derivazione

14

Una delle migliori architetture plug-in che ho visto è implementata in Eclipse. Invece di avere un'applicazione con un modello plug-in, tutto è un plug-in. L'applicazione di base stessa è il framework dei plug-in.

http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html


Certamente un buon esempio, ma è più grande e più complesso di quanto richiedono molte applicazioni?
justkt

Sì, potrebbe essere, l'aumento della flessibilità ha un costo. Ma penso che mostri un punto di vista diverso. Perché il menu dell'applicazione non può essere un plug-in o la vista principale?
Patrick,

6
Lo stesso approccio (tutto è un plugin) è usato nella nuova versione del framework Symfony. Si chiamano bundle ma il principio è lo stesso. La sua architettura è abbastanza semplice, forse dovresti dare un'occhiata: symfony-reloaded.org/quick-tour-part-4
Marijn Huizendveld,

@Marjin Huizendveld - dovresti aggiungere questo come una risposta separata in modo che io possa votare la risposta. Sembra un quadro interessante!
appena

11

Descriverò una tecnica abbastanza semplice che ho usato in passato. Questo approccio utilizza la riflessione C # per facilitare il processo di caricamento dei plugin. Questa tecnica può essere modificata in modo che sia applicabile al C ++ ma perdi la comodità di poter usare la riflessione.

Un'interfaccia IPluginviene utilizzata per identificare le classi che implementano i plugin. I metodi vengono aggiunti all'interfaccia per consentire all'applicazione di comunicare con il plugin. Ad esempio il Initmetodo che l'applicazione utilizzerà per istruire il plug-in all'inizializzazione.

Per trovare plug-in, l'applicazione esegue la scansione di una cartella di plug-in per gli assembly .Net. Ogni assieme viene caricato. Reflection viene utilizzato per cercare le classi che implementano IPlugin. Viene creata un'istanza di ogni classe di plugin.

(In alternativa, un file Xml potrebbe elencare gli assembly e le classi da caricare. Ciò potrebbe aiutare le prestazioni ma non ho mai riscontrato un problema con le prestazioni).

Il Initmetodo viene chiamato per ogni oggetto plugin. Viene passato un riferimento a un oggetto che implementa l'interfaccia dell'applicazione: IApplication(o qualcos'altro chiamato specifico per la tua app, ad esempio ITextEditorApplication).

IApplicationcontiene metodi che consentono al plugin di comunicare con l'applicazione. Ad esempio, se stai scrivendo un editor di testo, questa interfaccia avrebbe una OpenDocumentsproprietà che consente ai plug-in di enumerare la raccolta di documenti attualmente aperti.

Questo sistema di plug-in può essere esteso ai linguaggi di scripting, ad esempio Lua, creando una classe di plug-in derivata, ad esempio LuaPluginche inoltra le IPluginfunzioni e l'interfaccia dell'applicazione a uno script Lua.

Questa tecnica consente di implementare in modo iterativo i vostri IPlugin, IApplicatione altre interfacce applicazioni specifiche durante lo sviluppo. Quando l'applicazione è completa e ben riformata, puoi documentare le tue interfacce esposte e dovresti avere un bel sistema per il quale gli utenti possono scrivere i propri plugin.


7

Di solito uso MEF. Managed Extensibility Framework (o in breve MEF) semplifica la creazione di applicazioni estensibili. MEF offre funzionalità di individuazione e composizione che è possibile sfruttare per caricare le estensioni dell'applicazione.

Se sei interessato leggi di più ...


Grazie, sembra interessante. Quanto è facile applicare principi simili in altre lingue?
justkt

In realtà MEF è solo per .NET framework Non so nulla di altri framework
BALKANGraph

7

Una volta ho lavorato a un progetto che doveva essere così flessibile nel modo in cui ogni cliente poteva configurare il sistema, che l'unico buon progetto che abbiamo trovato è stato quello di spedire al cliente un compilatore C #!

Se la specifica è piena di parole come:

  • Flessibile
  • Collegare
  • personalizzabile

Poni molte domande su come supporterai il sistema (e su come verrà addebitato il supporto, poiché ogni cliente penserà che il suo caso sia il caso normale e non dovrebbe richiedere alcun plug-in.), Come nella mia esperienza

Il supporto dei clienti (o delle persone di supporto della linea di base) che scrivono plug-in è molto più difficile dell'architettura


5

Nella mia esperienza, i due modi migliori per creare un'architettura di plug-in flessibile sono linguaggi e librerie di scripting. Questi due concetti sono nella mia mente ortogonali; i due possono essere mescolati in qualsiasi proporzione, un po 'come una programmazione funzionale e orientata agli oggetti, ma trovano i loro maggiori punti di forza quando sono bilanciati. Una libreria è in genere responsabile per l'adempimento di una specifica interfaccia con funzionalità dinamiche, mentre gli script tendono a sottolineare la funzionalità con un'interfaccia dinamica.

Ho scoperto che un'architettura basata su script che gestiscono librerie sembra funzionare al meglio. Il linguaggio di scripting consente la manipolazione di alto livello delle librerie di livello inferiore e le librerie vengono quindi liberate da qualsiasi interfaccia specifica, lasciando tutte le interazioni a livello di applicazione nelle mani più flessibili del sistema di scripting.

Affinché ciò funzioni, il sistema di scripting deve disporre di un'API abbastanza solida, con hook ai dati dell'applicazione, alla logica e alla GUI, nonché le funzionalità di base dell'importazione e dell'esecuzione del codice dalle librerie. Inoltre, gli script sono generalmente tenuti a essere sicuri, nel senso che l'applicazione può recuperare con garbo da uno script scritto male. L'uso di un sistema di scripting come livello di indirizzamento significa che l'applicazione può staccarsi più facilmente in caso di Something Bad ™.

Il modo di impacchettare i plugin dipende in gran parte dalle preferenze personali, ma non si può mai sbagliare con un archivio compresso con una semplice interfaccia, diciamo PluginName.extnella directory principale.


3

Penso che devi prima rispondere alla domanda: "Quali componenti dovrebbero essere plug-in?" Vuoi mantenere questo numero al minimo assoluto o il numero di combinazioni che devi testare esplode. Prova a separare il tuo prodotto principale (che non dovrebbe avere troppa flessibilità) dalla funzionalità del plugin.

Ho scoperto che il principio IOC (Inversion of Control) (leggi springframework) funziona bene per fornire una base flessibile, che puoi aggiungere specializzazione per semplificare lo sviluppo del plugin.

  • È possibile eseguire la scansione del contenitore per il meccanismo "interfaccia come pubblicità di tipo plug-in".
  • È possibile utilizzare il contenitore per iniettare dipendenze comuni che potrebbero richiedere plug-in (ad esempio ResourceLoaderAware o MessageSourceAware).

1

Il modello plug-in è un modello software per estendere il comportamento di una classe con un'interfaccia pulita. Spesso il comportamento delle classi viene esteso dall'ereditarietà delle classi, in cui la classe derivata sovrascrive alcuni dei metodi virtuali della classe. Un problema con questa soluzione è che è in conflitto con il nascondiglio dell'implementazione. Conduce anche a situazioni in cui la classe derivata diventa un luogo di raccolta di estensioni di comportamento non correlate. Inoltre, lo scripting viene utilizzato per implementare questo modello come menzionato sopra "Crea internals come linguaggio di scripting e scrivi tutte le cose di alto livello in quel linguaggio. Questo lo rende abbastanza modificabile e praticamente a prova di futuro". Le librerie utilizzano le librerie di gestione degli script. Il linguaggio di scripting consente la manipolazione di alto livello delle librerie di livello inferiore. (Anche come menzionato sopra)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.