Immagina di avere un modulo software, che è un plugin per alcuni programmi (simile a Eclipse) e vuoi che abbia un'API che altri plugin possono chiamare. Il tuo plug-in non è disponibile gratuitamente, quindi vuoi avere un modulo API separato, che è disponibile gratuitamente ed è l'unica cosa a cui altri plugin devono collegarsi direttamente - I client API possono compilare solo con il modulo API e non il modulo di implementazione, sul percorso di compilazione. Se l'API è costretta a evolversi in modi compatibili, i plugin client potrebbero persino includere il modulo API nei propri jar (per impedire qualsiasi possibilità di Error
s derivante dall'accesso a classi inesistenti).
La licenza non è l'unica ragione per mettere API e implementazione in moduli separati. Potrebbe essere che il modulo di implementazione sia complesso, con una miriade di dipendenze a sé stante. I plugin Eclipse di solito hanno pacchetti interni e non interni, in cui i pacchetti non interni sono simili a un modulo API (entrambi sono inclusi nello stesso modulo, ma potrebbero essere separati).
Ho visto alcune alternative diverse per questo:
-
L'API si trova in un pacchetto separato (o gruppo di pacchetti) dall'implementazione. Le classi API chiamano direttamente nelle classi di implementazione. L'API non può essere compilata dal sorgente (cosa che è auspicabile in alcuni casi non comuni) senza l'implementazione. Non è facile prevedere gli esatti effetti della chiamata ai metodi API quando l'implementazione non è installata, quindi i client di solito eviteranno di farlo.
package com.pluginx.api; import com.pluginx.internal.FooFactory; public class PluginXAPI { public static Foo getFoo() { return FooFactory.getFoo(); } }
-
L'API si trova in un pacchetto separato e utilizza la reflection per accedere alle classi di implementazione. L'API può essere compilata senza l'implementazione. L'uso della riflessione potrebbe causare un impatto sulle prestazioni (ma gli oggetti di riflessione possono essere memorizzati nella cache se si tratta di un problema. È facile controllare cosa succede se l'implementazione non è disponibile.
package com.pluginx.api; public class PluginXAPI { public static Foo getFoo() { try { return (Foo)Class.forName("com.pluginx.internal.FooFactory").getMethod("getFoo").invoke(null); } catch(ReflectiveOperationException e) { return null; // or throw a RuntimeException, or add logging, or raise a fatal error in some global error handling system, etc } } }
-
L'API consiste solo di interfacce e classi astratte, oltre a un modo per ottenere un'istanza di una classe.
package com.pluginx.api; public abstract class PluginXAPI { public abstract Foo getFoo(); private static PluginXAPI instance; public static PluginXAPI getInstance() {return instance;} public static void setInstance(PluginXAPI newInstance) { if(instance != null) throw new IllegalStateException("instance already set"); else instance = newInstance; } }
-
Come sopra, ma il codice cliente deve ottenere il riferimento iniziale da un'altra parte:
// API package com.pluginx.api; public interface PluginXAPI { Foo getFoo(); } // Implementation package com.pluginx.internal; public class PluginX extends Plugin implements PluginXAPI { @Override public Foo getFoo() { ... } } // Client code uses it like this PluginXAPI xapi = (PluginXAPI)PluginManager.getPlugin("com.pluginx"); Foo foo = xapi.getFoo();
-
Non farlo. Fai in modo che i client si colleghino direttamente al plug-in (ma impediscono comunque che chiamino metodi non API). Ciò renderebbe difficile per molti altri plug-in (e la maggior parte dei plugin open source) utilizzare l'API di questo plugin senza scrivere il proprio wrapper.