En Java, ¿cuáles son algunas buenas maneras de separar las API de la implementación de * proyectos completos *?

8

Imagine que tiene un módulo de software, que es un complemento de algún programa (similar a Eclipse) y desea que tenga una API que otros complementos puedan llamar. Su complemento no está disponible de forma gratuita, por lo que desea tener un módulo API separado, que está disponible gratuitamente y es lo único que otros complementos necesitan vincular directamente: los clientes API pueden compilar solo con el módulo API y no el módulo de implementación, en el camino de construcción. Si la API se ve obligada a evolucionar de manera compatible, los complementos del cliente podrían incluso incluir el módulo API en sus propios archivos jar (para evitar cualquier posibilidad de que se Errorproduzca el acceso a clases inexistentes).

Las licencias no son la única razón para colocar la API y la implementación en módulos separados. Podría ser que el módulo de implementación sea complejo, con innumerables dependencias propias. Los complementos de Eclipse generalmente tienen paquetes internos y no internos, donde los paquetes no internos son similares a un módulo API (ambos están incluidos en el mismo módulo, pero podrían separarse).

He visto algunas alternativas diferentes para esto:

  1. La API está en un paquete separado (o grupo de paquetes) de la implementación. Las clases API llaman directamente a las clases de implementación. La API no se puede compilar desde la fuente (lo cual es deseable en algunos casos poco comunes) sin la implementación. No es fácil predecir los efectos exactos de llamar a métodos API cuando la implementación no está instalada, por lo que los clientes generalmente evitarán hacerlo.

    package com.pluginx.api;
    import com.pluginx.internal.FooFactory;
    public class PluginXAPI {
        public static Foo getFoo() {
            return FooFactory.getFoo();
        }
    }
  2. La API está en un paquete separado y utiliza la reflexión para acceder a las clases de implementación. La API se puede compilar sin la implementación. El uso de la reflexión puede causar un impacto en el rendimiento (pero los objetos de reflexión se pueden almacenar en caché si es un problema. Es fácil controlar lo que sucede si la implementación no está disponible.

    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. La API consta solo de interfaces y clases abstractas, además de una forma de obtener una instancia de una clase.

    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. Lo mismo que arriba, pero el código del cliente necesita obtener la referencia inicial de otro lugar:

    // 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. No lo hagas Haga que los clientes se vinculen directamente al complemento (pero aún así evite que llamen a métodos que no sean API). Esto dificultaría que muchos otros complementos (y la mayoría de los complementos de código abierto) utilicen la API de este complemento sin escribir su propio contenedor.

usuario253751
fuente

Respuestas:

3

La respuesta a su pregunta depende de la definición de "bueno" en la pregunta "¿cuáles son algunas buenas maneras de separar las API de la implementación de ..."

Si "bueno" significa "pragmático fácil de implementar para usted como fabricante de la API", esto podría ser útil:

Como está utilizando Java donde las bibliotecas jar se cargan en tiempo de ejecución, sugeriría una alternativa ligeramente diferente:

  • La API consta solo de interfaces más una implementación ficticia de estas interfaces que no tiene una función real.

el cliente que usa su api puede compilar contra este frasco simulado y, en el tiempo de ejecución, puede reemplazar el frasco ficticio con el licenciado.

algo similar se hace con slf4j donde se elegirá la implementación de registro real que se utilizará con su aplicación reemplazando el jar de registro

k3b
fuente
Para ampliar el ejemplo de slf4j : slf4j contiene implementaciones ficticias en las fuentes que luego se eliminan después de la compilación. El efecto de eso es una referencia fuerte (que da como resultado un ClassDefNotFound si no hay una implementación provista en su aplicación)
dot_Sp0T
@ dot_Sp0T Acepto que el problema "ClassDefNotFound" existe para Android porque en Android todos los frascos se fusionan en un solo archivo apk. En Java ordinario (sin usar un ofuscador) esto no debería ser un problema. Si estoy equivocado, por favor avísenme en qué circunstancias suceden.
k3b
4

¿Has echado un vistazo a los mecanismos de ServiceLoader en Java? Básicamente, puede especificar la implementación de una interfaz a través del archivo de manifiesto en un jar. Oracle también proporciona más información sobre complementos en programas Java.

Benni
fuente
1

Por lo que entiendo, la gente a menudo usa el patrón de fábrica para esto.

Pusieron las interfaces API en un módulo separado (digamos un archivo jar), y luego, cuando los clientes quieren usar la API y tienen acceso a una implementación de la API, la implementación de la API tendrá un punto de entrada de fábrica para que los clientes puedan usarla. comenzar a crear objetos concretos implementando esa API.

Esto significa que su primera idea de lo anterior es el parecido más cercano.

InformadoA
fuente