9

私はBlochのEffective Java book [1]を読んでいて、次のSPIの例に出くわしました:

//Service interface
public interface Service {
  //Service specific methods here
}

//Service provider interface
public interface Provider {
  Service newService();
}

//Class for service registration and access
public class Services {
  private Services(){}

  private static final Map<String, Provider> providers =
    new ConcurrentHashMap<String, Provider>();
  public static final String DEFAULT_PROVIDER_NAME = "<def>";

  //Registration
  public static void registerDefaultProvider(Provider p) {
    registerProvider(DEFAULT_PROVIDER_NAME, p);
  }
  public static void registerProvider(String name, Provider p) {
    providers.put(name, p);
  }

  //Access
  public static Service newInstance() {
    return newInstance(DEFAULT_PROVIDER_NAME);
  }
  public static Service newInstance(String name) {
     // you get the point..lookup in the map the provider by name
     // and return provider.newService();
  }

これは私の質問です: なぜ Provider インターフェースが必要なのですか? Service(s) 自体を簡単に登録することはできませんでしたか? たとえば、Service 実装のマップを維持し、検索したときにインスタンスを返すことはできませんでしたか? なぜ追加の抽象化レイヤーが必要なのですか?

おそらく、この例はあまりにも一般的です。要点を説明するための「より良い」例も素晴らしいでしょう。


[1] 第 2 版、第 2 章。第 1 版の例では、サービス プロバイダー インターフェイスについて言及していません。

4

6 に答える 6

1

各タイプの複数のサービスが必要になる可能性がある場合、古いサービスをそのまま再利用することはできません。(さらに、テストなどでは、以前のテストで変更または更新された可能性のあるサービスを再利用するのではなく、テストごとに新しいサービスを作成する必要がある場合があります。)

于 2012-07-07T16:04:46.417 に答える
0

したがって、同じサービスに対して複数Providerの s を持つことができService、特定のプロバイダー名に基づいて、同じサービスの異なるインスタンスを取得できるようです。したがって、各プロバイダーは、サービスを適切に作成する工場のようなものだと言えます。

たとえばclass PaymentService implements Service、 が必要だとしGatewayます。これらの支払い処理業者を扱う PayPal と Chase ゲートウェイがあります。次に、正しいゲートウェイで正しい PaymentService インスタンスを作成する方法をそれぞれが知っている PayPalProvider と ChaseProvider を作成します。

しかし、私は同意します、不自然に思えます。

于 2014-03-26T21:54:20.577 に答える
0

他の回答の統合として(4番目のコンポーネントはテキスト上の理由です)、これはコンパイルの依存関係を制限するためだと思います。SPI を使用すると、実装への明示的な参照を除外するためのすべてのツールを使用できます。

  • META-INF/services/ディレクトリには、利用可能なサービス プロバイダーの実装について言及しているファイルが含まれています。
  • ServiceLoader標準クラスにより、利用可能な実装名の解決が可能になり、動的な構築[ 1]が可能になります。

SPI は初版では言及されていませんでした。静的ファクトリに関する項目に含めるのは、おそらく適切な場所ではありませんでした。本文中の DriverManager はヒントですが、ブロッホは深入りしません。ある意味では、環境に応じて、プラットフォームは一種のServiceLocator パターンを実装して、コンパイルの依存関係を減らします。抽象ファクトリに SPI を使用すると、モジュール性のために ServiceLoader の助けを借りて ServiceLocator の ServiceFactory になります。

ServiceLoader イテレータを使用して、例のサービスマップを動的に設定できます。


[1] OSGi 環境では、これは微妙な操作です。

于 2014-12-17T13:56:43.480 に答える
-1

プロバイダーなしのサービス プロバイダー インターフェイス

プロバイダーなしでどのように見えるか見てみましょう。

//Service interface
public interface Service {
  //Service specific methods here
}

//Class for service registration and access
public class Services {
  private Services(){}

  private static final Map<String, Service> services =
    new ConcurrentHashMap<String, Service>();
  public static final String DEFAULT_SERVICE_NAME = "<def>";

  //Registration
  public static void registerDefaultService(Provider p) {
    registerService(DEFAULT_SERVICE_NAME, p);
  }
  public static void registerService(String name, Provider p) {
    services.put(name, p);
  }

  //Access
  public static Service getInstance() {
    return newInstance(DEFAULT_SERVICE_NAME);
  }
  public static Service getInstance(String name) {
     // you get the point..lookup in the map the service by name
     // and return it;
  }

ご覧のように、プロバイダー インターフェースなしでサービス プロバイダー インターフェースを作成することは可能です#getInstance(..)最終的にの呼び出し元は、違いに気付かないでしょう。

では、なぜプロバイダーが必要なのでしょうか?

ProviderインターフェイスはAbstract FactoryServices#newInstance(String)あり、Factory Methodです。どちらの設計パターンにも、サービスの実装をサービスの登録から切り離すという利点があります。

単一責任の原則

すべてのサービスを登録するスタートアップ イベント ハンドラーでサービスのインスタンス化を実装する代わりに、サービスごとに 1 つのプロバイダーを作成します。これにより、疎結合になり、リファクタリングが容易になります。たとえば、別の JAR ファイルにサービスとサービス プロバイダーを近づけることができるからです。

「ファクトリ メソッドは、ライブラリ コードが、フレームワークを使用するアプリケーションによってサブクラス化される可能性のある型のオブジェクトを作成する必要があるツールキットやフレームワークで一般的です。」[1]

生涯管理:

プロバイダーのない上のコードで、プロバイダーではなくサービスインスタンスを登録していることに気付いたかもしれません。プロバイダーは、新しいサービス インスタンスのインスタンス化を決定する可能性があります。

このアプローチにはいくつかの欠点があります。

1. サービス インスタンスは、最初のサービス呼び出しの前に作成する必要があります。遅延初期化はできません。これにより、起動が遅くなり、めったに使用されない、またはまったく使用されないサービスにリソースがバインドされます。

1b. サービスを再インスタンス化する方法がないため、使用後にサービスを閉じることはできません。(プロバイダーを使用すると、呼び出し元が を呼び出さなければならない方法でサービス インターフェイスを設計できます。これにより、プロバイダー#close()に通知され、プロバイダーはサービス インスタンスを保持またはファイナライズすることを決定します。)

2. すべての呼び出し元が同じサービス インスタンスを使用するため、スレッドセーフであることを確認する必要があります。ただし、スレッドセーフにすると遅くなります。逆に、プロバイダーは保持時間を短縮するために、いくつかのサービス インスタンスを作成することを選択する場合があります。

結論

プロバイダー インターフェイスは必須ではありませんが、サービス固有のインスタンス化ロジックをカプセル化し、リソース割り当てを最適化します。

于 2014-03-27T08:35:00.257 に答える