按照 Venkat Subramaniam 的书使用 lambda 表达式玩 Strategy Design Pattern?

Playing with Strategy Design Pattern using lambda expression by following Venkat Subramaniam's book?

我正在关注 Subramaniam 教授的 book。书中教授试图解释Delegating Using Lambda Expressions.

的原理

We used lambda expressions and the strategy pattern to separate a concern from a method. We can also use them to separate a concern from a class. From a reuse point of view, delegation is a better design tool than inheritance. With delegation it’s easier to vary the implementation we rely on, and we can plug in a different behavior more dynamically. This can help vary the behavior of classes independent of the behavior of the parts they depend on, and make the design more flexible without forcing a deep class hierarchy

这是一个特定的 class 静态方法,它执行所需的 calculation/fetching 信息。

 public class YahooFinance {
        public static BigDecimal getPrice(final String ticker) {
            try {
                final URL url = new URL("http://ichart.finance.yahoo.com/table.csv?s=" + ticker);
                final BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
                final String data = reader.lines().skip(1).findFirst().get();
                final String[] dataItems = data.split(",");
                return new BigDecimal(dataItems[dataItems.length - 1]);
            } catch(Exception ex) {
                throw new RuntimeException(ex);
            }
        }
  }

Creating a Delegate: Rather than delegating part of the responsibility to another class, we can delegate it to lambda expressions and method references. This will further reduce class proliferation.

这是客户端代码(即 Delegate):

public class CalculateNAV {
    

    private Function<String, BigDecimal> priceFinder;
    
      public CalculateNAV(final Function<String, BigDecimal> aPriceFinder) {
          priceFinder = aPriceFinder;
      }

      public BigDecimal computeStockWorth(final String ticker, final int shares) {
    
          return priceFinder.apply(ticker).multiply(BigDecimal.valueOf(shares));
      }
  //... other methods that use the priceFinder ...


  public static void main(String[] args) {
    final CalculateNAV calculateNav = new CalculateNAV(YahooFinance::getPrice);
    
    System.out.println(String.format("100 shares of Google worth: $%.2f",
      calculateNav.computeStockWorth("GOOG", 100)));
  }
}

我的领域问题在某种程度上与教授试图做的非常相似。

我的 Spring 应用程序中有三个实体(InvoiceTourInvoiceLabTestInvoicePenatly)。它们具有不同的属性并且没有任何关系,因此没有继承,没有接口。但我确实需要将它们发送到某个 SOAP Web 服务,该服务将为这些实体中的每一个提供 return 相同的对象类型 (InsertInvoiceResponse)。 SoapHelper class 很像 YahooFinance.

public class SoapHelper {
    
    public InsertInvoiceResponse getValueForTour(InvoiceTour invoiceTourEntity, Company company) {

        //Some processing of passed parameters which results in InsertInvoiceRequest (requestPayload) object (stub for soap service)

        return send(requestPayload);
    }

    public InsertInvoiceResponse getValueForLabTest(InvoiceLabTest invoiceLabTestEntity, Company company) {

        //Some processing of passed parameters which results in InsertInvoiceRequest (requestPayload) object (stub for soap service)

        return send(requestPayload);
    }

    public InsertInvoiceResponse getValueForPenalty(InvoicePenalty invoicePenalty Entity, Company company) {

        //Some processing of passed parameters which results in InsertInvoiceRequest (requestPayload) object (stub for soap service)

        return send(requestPayload);
    }
}

方法send(requestPayload)是这样的:

//Spring's abstraction for sending SOAP requests
public InsertInvoiceResponse send(InsertInvoiceRequest requestPayload) {
    return (InsertInvoiceResponse) getWebServiceTemplate()
    .marshalSendAndReceive("https://clienttesthorizon.horizonafs.com/AFSServices/AFSService.svc/basicHttpBinding", 
            requestPayload, new SoapActionCallback("http://tempuri.org/IAFSService/InsertInvoice")); 
}

我做了什么?

首先我创建了功能接口,像这样:

@FunctionalInterface
public interface Executor<A,B,C> { //A - stands for any InvoiceXXX; B - Company parameter and C will be result of soap response (InsertInvoiceResponse)
    
    C apply(A a, B b);
}

接下来,我创建了一个class,它负责使用方法引用来调用实际的方法实现。 class SoapCaller 很像 CalculateNAVSoapCaller 将具有 Executor 类型的私有字段,但我想让它更通用。我知道接口本身已经是通用的,但由于缺乏文字,我不确定如何表达不同。

我的想法是能够像这样传递给 SoapCaller 的构造函数:

   public class SoapCaller {
    
        private Executor<A, B, C> exec;
        
        public SoapCaller(final Executor<Class<T> t, Class<F> f, Class<E> e> execGeneric) {
            this.exec = execGeneric;
        }
        
        public InsertInvoiceResponse callWebService(Class<T> t,  Class<F> f) {
            
          return exec.apply(t, f);
        }
        
    }

我的客户端代码应该如下所示:

public static void main(String[] args) {
    
    InvoiceTour it = db.getInvoiceTour();
    InvoiceLabTest ilt = db.getInvoiceLabTest();
    InvoicePenalty ip = db.getInvoicePenalty();
    
    Company c = db.getCompany();
    
    SoapCaller soapCallerForInvoiceTour = new SoapCaller(SoapHelper::getValueForTour);
    
    InsertInvoiceResponse invoiceTourResponse = soapCallerForInvoiceTour.callWebService(it, c);
    
    //do what ever with invoiceTourResponse
    
    SoapCaller soapCallerForInvoiceLabTest= new SoapCaller(SoapHelper::getValueForLabTest);
    
    InsertInvoiceResponse invoiceLabTestResponse = soapCallerForInvoiceTour.callWebService(ilt, c);
    
    //do what ever with invoiceLabTestResponse
    
}

当然有编译错误的语气。我不确定如何实现使功能接口比现在更通用(如果这有意义的话)?有没有人知道如何使用 lambda 和方法引用使它更具可读性?

如果我理解你的话,你希望 SoapCaller(委托给 lambda 的 class)也是通用的,所以它可以定义如下:

class SoapCaller<T, U> {

    private BiFunction<T, U, InsertInvoiceResponse> soapExecutor;
    
    public SoapCaller(final BiFunction<T, U, InsertInvoiceResponse> soapExecutor) {
        this.soapExecutor= soapExecutor;
    }
    
    public InsertInvoiceResponse callWebService(T t, U u) {
      return soapExecutor.apply(t, u);
    }
    
}

请注意,您可以使用 BiFunction 而不是定义您自己的功能接口。

那你就可以这样使用了:

SoapCaller<InvoiceTour, Company> soapCallerForInvoiceTour = new SoapCaller<>(SoapHelper::getValueForTour);
InsertInvoiceResponse invoiceTourResponse = soapCallerForInvoiceTour.callWebService(it, c);
    
//do what ever with invoiceTourResponse
    
SoapCaller<InvoiceLabTest, Company> soapCallerForInvoiceLabTest= new SoapCaller<>(SoapHelper::getValueForLabTest);
InsertInvoiceResponse invoiceLabTestResponse = soapCallerForInvoiceLabTest.callWebService(ilt, c);

你可以尝试使用通用参数,例如,如果你知道 Company 不会改变,那么你可以删除类型参数:

class SoapCaller<T> {

    private BiFunction<T, Company, InsertInvoiceResponse> soapExecutor;

    ...
}