Java编程接口

示例

对接口进行编程的思想是使代码主要基于接口,并且仅在实例化时使用具体的类。在这种情况下,处理例如Java集合的良好代码将如下所示(该方法本身根本没有用,只是说明):

public <T> Set<T> toSet(Collection<T> collection) {
  return Sets.newHashSet(collection);
}

而错误的代码可能看起来像这样:

public <T> HashSet<T> toSet(ArrayList<T> collection) {
  return Sets.newHashSet(collection);
}

前者不仅可以应用于更多的参数选择,而且其结果将与通常遵循接口编程概念的其他开发人员提供的代码更加兼容。但是,使用前者的最重要原因是:

  • 在大多数情况下,使用结果的上下文不需要且不需要具体实现所提供的那么多细节;

  • 坚持使用接口可以使代码更简洁,黑客攻击更少,例如,在为某些特定情况提供服务的类中添加了另一个公共方法;

  • 该代码更易于测试,因为接口易于模拟;

  • 最后,即使只希望实现一种实现方式(至少出于可测试性),该概念也会有所帮助。

因此,在编写新代码时要牢记一个特定的实现,如何轻松地将编程的概念应用于接口?我们通常使用的一种选择是以下模式的组合:

  • 编程到接口

  • 建造者

以下基于这些原理的示例是针对许多不同协议编写的RPC实现的简化和截断版本:

public interface RemoteInvoker {
  <RQ, RS> CompletableFuture<RS> invoke(RQ request, Class<RS> responseClass);
}

上面的接口不应该直接通过工厂实例化,而是我们进一步推导更具体的接口,一个接口用于HTTP调用,一个接口用于AMQP,每个接口都有一个工厂和一个构造器来构造实例,而这些构造器又是上面的界面:

public interface AmqpInvoker extends RemoteInvoker {
  static AmqpInvokerBuilder with(String instanceId, ConnectionFactory factory) {
    return new AmqpInvokerBuilder(instanceId, factory);
  }
}

RemoteInvoker现在,可以轻松构造用于AMQP的实例(或更复杂的实例,具体取决于构建者):

RemoteInvoker invoker = AmqpInvoker.with(instanceId, factory)
  .requestRouter(router)
  .build();

调用请求很简单:

Response res = invoker.invoke(new Request(data), Response.class).get();

由于Java 8允许将静态方法直接放置在接口中,因此在上述代码中,中间工厂已变为隐式,用代替。在版本8之前的Java中,使用内部类可以实现相同的效果:AmqpInvoker.with()Factory

public interface AmqpInvoker extends RemoteInvoker {
  class Factory {
    public static AmqpInvokerBuilder with(String instanceId, ConnectionFactory factory) {
      return new AmqpInvokerBuilder(instanceId, factory);
    }
  }
}

相应的实例将变成:

RemoteInvoker invoker = AmqpInvoker.Factory.with(instanceId, factory)
  .requestRouter(router)
  .build();

上面使用的构建器可能看起来像这样(尽管这是一种简化,因为实际的构建器允许定义多达15个与默认值不同的参数)。请注意,该构造不是公开的,因此仅可从上述AmqpInvoker接口中使用:

public class AmqpInvokerBuilder {
  ...
  AmqpInvokerBuilder(String instanceId, ConnectionFactory factory) {
   this.instanceId= instanceId;
   this.factory= factory;
  }

  public AmqpInvokerBuilder requestRouter(RequestRouter requestRouter) {
   this.requestRouter= requestRouter;
    return this;
  }

  public AmqpInvoker build() throws TimeoutException, IOException {
    return new AmqpInvokerImpl(instanceId, factory, requestRouter);
  }
}

通常,还可以使用FreeBuilder之类的工具来生成构建器。

最后,此接口的标准(也是唯一预期的)实现定义为程序包本地类,以强制使用接口,工厂和构建器:

class AmqpInvokerImpl implements AmqpInvoker {
  AmqpInvokerImpl(String instanceId, ConnectionFactory factory, RequestRouter requestRouter) {
    ...
  }

  @Override
  public <RQ, RS> CompletableFuture<RS> invoke(final RQ request, final Class<RS> respClass) {
    ...
  }
}

同时,无论功能多么简单或复杂,事实证明这种模式对于开发所有新代码都是非常有效的。