In Java, quali sono alcuni buoni modi per separare le API dall'implementazione di * interi progetti *?

8

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:

  1. 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();
        }
    }
    
  2. 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
            }
        }
    }
    
  3. 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;
        }
    }
    
  4. 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();
    
  5. 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.

posta immibis 02.07.2014 - 10:05
fonte

3 risposte

3

La risposta alla tua domanda dipende dalla definizione di "buono" nella domanda "quali sono alcuni buoni modi per separare le API dall'implementazione di ..."

Se "buono" significa "pragmatico facile da implementare per te come produttore di api", questo potrebbe essere utile:

dato che stai usando java dove le librerie jar sono caricate in fase di runtime ti suggerirei un'alternativa leggermente diversa:

  • L'API consiste solo di interfacce più un'implementazione fittizia di queste interfacce che non ha alcuna funzione reale.

il cliente che utilizza la tua API può compilare questo dummy-jar e in fase di runtime puoi sostituire il dummy-jar con quello con licenza.

qualcosa di simile è fatto con slf4j dove l'effettiva implementazione di log da utilizzare con la tua applicazione sarà scelta sostituendo il jar di registrazione

    
risposta data 02.07.2014 - 15:41
fonte
4

Hai dato un'occhiata al ServiceLoader Meccanismi in Java ? Fondamentalmente è possibile specificare l'implementazione di un'interfaccia tramite il file manifest in un jar. Oracle fornisce anche ulteriori informazioni sui plugin nei programmi Java.

    
risposta data 02.07.2014 - 11:10
fonte
1

Da quello che capisco le persone usano spesso il modello di fabbrica per questo.

Inseriscono le interfacce API in un modulo separato (ad esempio un file jar), quindi quando i client desiderano utilizzare l'API e hanno accesso a un'implementazione dell'API, l'implementazione dell'API avrà un punto di ingresso di fabbrica per i clienti iniziano a creare oggetti concreti che implementano quell'API.

Ciò significa che la tua prima idea da sopra è la somiglianza più vicina.

    
risposta data 02.07.2014 - 10:26
fonte

Leggi altre domande sui tag