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


8

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

ライセンスは、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を使用することが困難になります。

回答:


3

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

「良い」が「APIの製造者として実装するのにプラグマティックで簡単」を意味する場合、これは役立つかもしれません:

実行時にjar-librariesがロードされるjavaを使用しているので、少し異なる代替案を提案します。

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

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

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


slf4jの例を拡張するには:slf4jにはソースにダミーの実装が含まれており、コンパイル後に削除されます。その影響は強い参照です(アプリケーションに実装が提供されていない場合はClassDefNotFoundになります)
dot_Sp0T

@ dot_Sp0T androidではすべてのjarが1つのapkファイルにマージされるため、androidに「ClassDefNotFound」問題が存在することに同意します。通常のJavaでは(難読化ツールを使用しない場合)、これは問題になりません。私が間違っている場合は、状況が発生することを知ってください。
k3b 2015年

4

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


1

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.