web-dev-qa-db-ja.com

Javaでは、*プロジェクト全体*の実装からAPIを分離するいくつかの良い方法は何ですか?

あるプログラムへのプラグイン(Eclipseに似ています)であるソフトウェアモジュールがあり、他のプラグインが呼び出すことができるAPIが欲しいと想像してください。プラグインは自由に利用できないため、別のAPIモジュールが必要です。これはis自由に利用でき、他のプラグインが直接リンクする必要がある唯一のものです-APIクライアントはAPIモジュールのみでコンパイルできますビルドパス上の実装モジュールではなく、。 APIが互換性のある方法で進化するように制約されている場合、クライアントプラグインはAPIモジュールを独自のjarに含めることもできます(存在しないクラスがアクセスされることによりErrorsが発生する可能性を防ぐため)。

ライセンスは、APIと実装を別々のモジュールに配置する唯一の理由ではありません。実装モジュールが複雑で、独自の無数の依存関係がある可能性があります。 Eclipseプラグインには通常、内部パッケージと非内部パッケージがあり、非内部パッケージはAPIモジュールに似ています(どちらも同じモジュールに含まれていますが、分離することができます)。

私はこれのいくつかの異なる選択肢を見てきました:

  1. APIは、実装とは別のパッケージ(またはパッケージのグループ)にあります。 APIクラスは、実装クラスを直接呼び出します。 API できませんは、実装なしでソースからコンパイルされます(まれなケースでは望ましい)。実装がインストールされていないときにAPIメソッドを呼び出すことの正確な影響を予測することは簡単ではありません。そのため、クライアントは通常、これを回避します。

    package com.pluginx.api;
    import com.pluginx.internal.FooFactory;
    public class PluginXAPI {
        public static Foo getFoo() {
            return FooFactory.getFoo();
        }
    }
    
  2. APIは別のパッケージにあり、リフレクションを使用して実装クラスにアクセスします。 APIは実装なしでコンパイルできます。リフレクションを使用すると、パフォーマンスが低下する可能性があります(ただし、問題がある場合はリフレクションオブジェクトをキャッシュできます。実装が利用できない場合の動作を簡単に制御できます。

    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. APIは、インターフェースと抽象クラスのみで構成され、さらにクラスのインスタンスを取得する方法も含まれます。

    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. 上記と同じですが、クライアントコードは別の場所から初期参照を取得する必要があります。

    // 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. しないでください。クライアントをプラグインに直接リンクさせる(ただし、非APIメソッドを呼び出せないようにする)。これにより、他の多くのプラグイン(およびほとんどのオープンソースプラグイン)が、独自のラッパーを作成せずにこのプラグインのAPIを使用することが困難になります。

8
user253751

あなたの質問への答えは、「APIを...の実装から分離するいくつかの良い方法は何か」という質問の「良い」の定義に依存します。

「良い」が「APIの製造者として実装するためのプラグマティックな簡単さ」を意味する場合、これは役立つ可能性があります。

Java jar-librariesが実行時にロードされる場所を使用しているため、少し異なる代替案を提案します。

  • APIは、インターフェースと、実際の機能を持たないこれらのインターフェースのダミー実装のみで構成されています。

aPIを使用しているお客様は、このダミーjarに対してコンパイルでき、実行時にダミーjarをライセンス済みのjarに置き換えることができます。

slf4j でも同様の処理が行われます。アプリケーションで使用される実際のロギング実装は、logging-jarを置き換えることで選択されます。

3
k3b

Javaの ServiceLoader メカニズムを確認しましたか?基本的には、jarのマニフェストファイルを介してインターフェースの実装を指定できます。オラクルは、Javaプログラムのプラグインについて、さらに 情報 も提供しています。

4
Benni

私が理解していることから、人々はこのためにファクトリーパターンをよく使用します。

APIインターフェースを個別のモジュール(jarファイルなど)に配置し、クライアントがAPIを使用したいときに、APIの実装にアクセスできる場合、APIの実装には、クライアントが実行するためのファクトリエントリポイントがあります。そのAPIを実装する具体的なオブジェクトの作成を開始します。

これは、上記の最初のアイデアが最も類似していることを意味します。

1
InformedA