没有Provider的服务提供者接口

oym*_*oym 8 java design-patterns service-provider

我正在阅读Bloch的有效java书[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();
  }
Run Code Online (Sandbox Code Playgroud)

这是我的问题:为什么必须使用Provider接口?难道我们不能轻易地自己注册服务 - 例如,维护服务实现的映射,然后在查找时返回实例?为什么要额外的抽象层?

也许这个例子过于通用 - 任何"更好"的例子来说明这一点也会很棒.


[1] 第二版,第2章.第一版示例未涉及服务提供者接口.

dka*_*zel 5

为什么需要提供者接口?我们是否可以像注册自己一样轻松地注册服务(例如,维护服务实现的映射,然后在查找时返回实例)?

正如其他人所述,Provider的目的是拥有一个可以创建Service实例的AbstractFactory 。您并不总是希望保留对所有Service实现的引用,因为它们可能是短暂的和/或在执行后可能不可重用。

但是提供者的目的是什么,如果没有提供者,如何使用“提供者注册API”

拥有Provider接口的最强大的理由之一就是您不需要在编译时就实现。您的API用户可以稍后添加自己的实现。

让我们使用JDBC作为示例,就像在另一个答案中使用的Ajay一样,但让我们更进一步:

有许多不同类型的数据库和数据库供应商,它们的管理和实现数据库(以及如何查询数据库)的方式略有不同。Java的创建者可能由于多种原因无法创建所有这些不同可能方式的实现:

  • 最初编写Java时,其中许多数据库公司或系统尚不存在。
  • 并非所有这些数据库供应商都是开源的,因此Java的创建者即使愿意也无法与他们进行通信。
  • 用户可能要编写自己的自定义数据库

那么您如何解决呢?通过使用Service Provider

  • The Driver interface is the Provider. It provides methods for interacting with a particular vendor's databases. One of the methods in Driver is a factory method to make a Connection instance(which is the Service) to the database given a url and other properties (like user name and password etc).

Each Database vendor writes their own Driver implementation for how to communicate with their own database system. These aren't included in the JDK; you must go to the company websites or some other code repository and download them as a separate jar.

To use these drivers, you must add the jar to your classpath and then use the JDK DriverManager class to register the driver.

The DriverManager class has a method registerDriver(Driver) that is used to register a Driver instance in the Service Registration so it can be used. By convention, most Driver implementations register at class loading time so all you have to do in your code is write

Class.forname("foo.bar.Driver"); 
Run Code Online (Sandbox Code Playgroud)

To register the Driver for vendor "foo.bar" (assuming you have the jar with that class in your classpath.)

Once the Database Drivers are registered, you can get a Service implementation instance that is connected to your database.

For example, if you had a mysql database on your local machine named "test" and you had a user account with username "monty" and password "greatsqldb" then you can create a Service implementation like this :

Connection conn =
   DriverManager.getConnection("jdbc:mysql://localhost/test?" +
                               "user=monty&password=greatsqldb");
Run Code Online (Sandbox Code Playgroud)

DriverManager类查看您传入的String,并找到可以理解其含义的注册驱动程序。(这实际上是通过使用Chain of Responsibility模式通过遍历所有已注册的驱动程序并调用其Driver.acceptsUrl(Stirng)方法直到URL被接受来完成的)

注意,在JDK中没有mysql特定的代码。您要做的就是注册某个供应商的驱动程序,然后将正确格式的字符串传递给服务提供商。如果以后我们决定使用其他数据库供应商(例如oracle或sybase),则只需交换jar并修改我们的连接字符串。DriverManager中的代码不变。

我们为什么不只建立一次连接并保持联系呢?为什么我们需要服务?

We might want connect/disconnect after each operation. Or we might want to keep the connection around longer. Having the Service allows us to create new connections whenever we want and does not preclude us from keeping a reference to it to re-use later.

This is a very powerful concept and is used by frameworks to allow many possible permutations and extensions without cluttering the core codebase.

EDIT

Working with multiple Providers and Providers that provide multiple Services:

There is nothing stopping you from having multiple Providers. You can connect to multiple databases created using different database vendor software at the same time. You can also connect to multiple databases produced by the same vendor at the same time.

Multiple services - Some Providers may even provide different Service implementations depending on the connect url. For example, H2 can create both file system based or in-memeory based databases. The way to tell H2 which one you want to use is a different url format. I haven't looked at the H2 code, but I assume the file based and the in memory based are different Service implementations.

Why doesn't the DriverManager just manage Connections and Oracle could implement the OracleConnectionWrapper? No providers!

That would also require you to know that you have an Oracle connection. That is very tight coupling and I would have to change lots of code if I ever changed vendors.

The Service Registration just takes a String. Remember that it uses the chain of Responsiblity to find the first registered Provider that knows how to handle the url. the application can be vendor neutral, and it can get the connection url and Driver class name from a property file. That way I don't have to recompile my code if I change vendors. However, if I hardcoded refernences to "OracleConnectionWrapper" and then I changed vendors, I would have to rewrite portions of my code and then recompile.

There is nothing preventing someone from supporting multiple database vendor url formats if they want. So I can make a GenericDriver that could handle mysql and oracle if I wanted to.

  • 我喜欢您的详细信息,但仍然有些事情不是Bloch提出的。驾驶员是提供者。连接是服务。如果您只有服务接口,则您对提供程序所说的一切仍将工作类似:无需具体实现。 (2认同)

Lou*_*man 1

如果您可能需要每种类型的多个服务,则不能只重用旧服务。(此外,测试等可能希望为每个测试创建新的服务,而不是重用先前测试可能已修改或更新的服务。)