你的“最佳价格查询器”应用现在能从不同的商店取得商品价格,解析结果字符串,针对每个字符串,查询折扣服务取的折扣代码。这个流程决定了请求商品的最终折扣价格(每个折扣代码的实际折扣比率有可能发生变化,所以你每次都需要查询折扣服务)。我们已经将对商店返回字符串的解析操作封装到了下面的Quote
类之中:
public class Quote {
private final String shopName;
private final double price;
private final Discount.Code discountCode;
public Quote(String shopName, double price, Discount.Code code) {
this.shopName = shopName;
this.price = price;
this.discountCode = code;
}
public static Quote parse(String s) {
String[] split = s.split(":");
String shopName = split[0];
double price = Double.parseDouble(split[1]);
Discount.Code discountCode = Discount.Code.valueOf(split[2]);
return new Quote(shopName, price, discountCode);
}
public String getShopName() { return shopName; }
public double getPrice() { return price; }
public Discount.Code getDiscountCode() { return discountCode; }
}
通过传递shop
对象返回的字符串给静态工厂方法parse
,可以得到Quote
类的一个实例,它包含了shop
的名称、折扣之前的价格,以及折扣代码。Discount
服务还提供了一个applyDiscount
方法,它接收一个Quote
对象,返回一个字符串,表示生成该Quote
的shop
中的折扣价格,代码如下所示。
public class Discount {
public enum Code {
// 源码暂时省略……
}
public static String applyDiscount(Quote quote) {
return quote.getShopName() + " price is " +
Discount.apply(quote.getPrice(),
quote.getDiscountCode());
}
private static double apply(double price, Code code) {
delay();
return format(price * (100 - code.percentage) / 100);
}
}
由于Discount服务是一种远程服务,你还需要增加1秒钟的模拟延迟,代码如下所示。首先尝试以最直接的方式(坏消息是,这种方式是顺序而且同步执行的)重新实现findPrices,以满足这些新增的需求。
代码清单11-15 以最简单的方式实现使用Discount服务的findPrices方法
public List<String> findPrices(String product) {
return shops.stream()
.map(shop -> shop.getPrice(product)) //取得每个shop对象中商品的原始价格
.map(Quote::parse) //在Quote对象中对shop返回的字符串进行转换
.map(Discount::applyDiscount) //联系Discount服务,为每个Quote申请折扣
.collect(toList());
}
通过在shop
构成的流上采用流水线方式执行三次map
操作,我们得到了期望的结果。
shop
对象转换成了一个字符串,该字符串包含了该 shop
中指定商品的价格和折扣代码。Quote
对象中对它们进行转换。map
会操作联系远程的Discount
服务,计算出最终的折扣价格,并返回该价格及提供该价格商品的shop
。这种实现方式的性能远非最优,不过还是应该测量一下。跟之前一样,通过运行基准测试,得到下面的数据:
[BestPrice price is 110.93, LetsSaveBig price is 135.58, MyFavoriteShop price
is 192.72, BuyItAll price is 184.74, ShopEasy price is 167.28]
Done in 10028 msecs
毫无意外,这次执行耗时10秒,因为顺序查询5个商店耗时大约5秒,现在又加上了Discount
服务为5个商店返回的价格申请折扣所消耗的5秒钟。你已经知道,把流转换为并行流的方式,非常容易提升该程序的性能。不过,这一方案在商店的数目增加时,扩展性不好,因为Stream
底层依赖的是线程数量固定的通用线程池。相反,如果自定义CompletableFutures
调度任务执行的执行器能够更充分地利用CPU资源。