【问题标题】:Builder pattern in place of method overloading构建器模式代替方法重载
【发布时间】:2019-11-15 12:24:47
【问题描述】:

我有一个看起来像这样的接口 TaxCalculator

公共接口 TaxCalculator { int DEFAULT_AMOUNT_FOR_RATE_CALCULATION = 100;

/**
 *
 * @param cart the cart to calculate taxes for. Right now we are only calculating using the total taxable amount, but when we implement
 *             charging taxes properly, we should use data from the cart like line-item amounts, and types.
 * @return the tax rate to charge for this cart
 */
SalesTaxRates getSalesTaxes(ShoppingCart cart, Address address);
SalesTaxRates getSalesTaxes(ShoppingCart cart, String zipcode);

SalesTaxRates getSalesTaxes(Order order) throws Exception;



/** Since taxes ideally get calculated using information like line-item amounts, and types, you cannot get an accurate
 * rate from the taxable amount alone. This is a function we want to support, however, on pages such as calculate payboo savings.
 *
 * @param taxableAmount the amount to calculate taxes for
 * @return a quote for what taxes will likely come out to
 */
SalesTaxRates getSalesTaxes(double taxableAmount, String zipCode);

SalesTaxRates getSalesTaxes(String zipCode);

SalesTaxRates getSalesTaxes(Address address);

SalesTaxRates getSalesTaxes(double taxableAmount, Address address);

default SalesTaxRates getSalesTaxes(double taxableAmount, ZipCode zipCode){
    return getSalesTaxes(taxableAmount,zipCode.getZipCode());
}
default SalesTaxRates getSalesTaxes( ZipCode zipCode){
    return getSalesTaxes(zipCode.getZipCode());
}
default SalesTaxRates getSalesTaxes(ShoppingCart cart, ZipCode zipcode){
    return getSalesTaxes(cart,zipcode.getZipCode());
}

我有两种计算税收的实现,一种用于 api,一种是内部系统 我有第三个实现充当代理 - 在每次调用时它都像这样有效地工作

@Override
    public SalesTaxRates getSalesTaxes(ShoppingCart cart, Address address)
    {
        Map<String,SalesTaxRates> cache = getTaxSessionCache();
        String cacheKey = generateCacheKey(cart,address);
        if(cache.containsKey(cacheKey))
            return cache.get(cacheKey);
        TaxCallable taxCallable = new TaxCallable() {
            @Override
            public SalesTaxRates call() {
                return this.getTaxCalculator().getSalesTaxes(cart,address);
            }
        };
        SalesTaxRates taxRates = callTaxCalculators(taxCallable);
        if(taxRates.isCacheable())
            cache.put(cacheKey, taxRates);
        return taxRates;
    }

以及定义的方法:

private static SalesTaxRates callTaxCalculators(TaxCallable callable)
    {
        for (TaxCalculator taxCalculator: TaxEngine.getTaxCalculators())
        {
            FutureTask<SalesTaxRates> futureTask = new FutureTask<>(callable.setTaxCalculator(taxCalculator));
            new Thread(futureTask).start();
            try {
                SalesTaxRates taxRates = futureTask.get(getTaxCalculatorTimeout(), TimeUnit.MILLISECONDS);
                if(taxRates!=null && taxRates.isCalculated())
                {
                    BHLogger.info("Used taxcalculator: " + taxRates.getTaxEngine() + "rate: " + taxRates.getTotalTaxRate());
                    return taxRates;
                }
            } catch (Exception e) {
                BHLogger.error(e);
            }
        }
        BHLogger.error("ShoppingCart.calculteSalesTax","No taxCalculator calculated taxes correctly");
        return SalesTaxRates.NOT_CALCULATED;
    }

它检查缓存,看看它是否在那里,然后返回它。否则它调用转发消息并缓存它 所以这对于每个重载的方法来说似乎非常重复,但是我没有一种明显的方法来调用一个方法,因为缓存是不同的 我的经理建议我使用构建器模式。创建一个 TaxRequest 对象,该对象具有任何相关字段。它可以处理自己的缓存

我猜应该是这样的

class TaxRequest{
private Order order;
private Cart cart;
private Address address;
private String zipcode;
private SalesTaxRates send(TaxCalculator tc){
   //check cache
   //else
   tc.getSalesTaxes(this);
   //handlecaching
}
}

接口只需要一个方法,Forwarding类变得很简单 我猜 ApiTaxCalculator 实现现在需要通过这样的方法来处理这个问题

SalesTaxRates getSalesTax(TaxRequest taxRequest)
{
   if(taxRequest.hasOrder()
       returnGetSalesTaxes(taxRequest.getOrder);
  //if has address and cart... if has only address... if has just a zip...
}

基本上,建议是使用构建器模式而不是方法重载 但最后一种方法对我来说似乎很尴尬, 建议这样做的另一个原因是,内部 taxCalculator 对订单的处理与仅对邮政编码的处理没有任何不同。理想情况下,它应该考虑项目类型或折扣等因素,但事实并非如此。它只是根据邮政编码查找费率 所以该类中有很多开销只是将其转发到该方法

有什么建议吗?

【问题讨论】:

  • 不确定我是否可以关注你(这是相当大的文本),但你的基本接口只能有一个 StringWithZipCode 重载,而 WithZipCode 是一个接口,将由ZipCodeOrder 等仅提供 getter String getZipCode()。然后,您的可调用对象只需要接受和缓存 WithZipCode 类型的对象。因为WithZipCode 只有一个函数可以通过String 来传递原始() -&gt; zipCodeString。如果有不清楚的地方给我提个醒,我会提供示例代码
  • 您可以将缓存留给TaxCalculator,并基于TaxRequest 构建缓存键。
  • 对不起,如果我不清楚。这些方法非常不同。你提供的信息越多越好。例如,给一个订单,我可以看到哪些项目是应税的,你有什么折扣,你的运费等等。我不只是深入到邮政编码,这就是其中一个实现所发生的事情。但客户不应该知道这一点
  • 这是给图书馆的吗?我要理智,他们不应该知道吗?他们可以检查代码
  • 知道他们不必处理调用正确的函数?

标签: java design-patterns interface coding-style builder-pattern


【解决方案1】:

看看这个:

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

class StackOverflowQuestion56893388Scratch {
    static class SalesTaxRates {
        //TODO: don't know anything about it
    }

    interface WithZipCode {
        String getZipCode();
    }

    interface WithShipping {
        Optional<Integer> getShippingCosts();
    }

    interface WithDiscount {
        Optional<Integer> getDiscount();
    }

    static class ZipCode implements WithZipCode {
        /*TODO: return some field*/
        @Override
        public String getZipCode() { return null; }
    }

    //I chose arbitrary interfaces here as I don't know more about your domain
    static class ShoppingCart implements WithZipCode, WithShipping {
        /*TODO: return some field*/
        @Override
        public String getZipCode() { return null; }

        /*TODO: return some field*/
        @Override
        public Optional<Integer> getShippingCosts() { return Optional.empty(); }
    }

    //I chose arbitrary interfaces here as I don't know more about your domain
    static class Order implements WithZipCode, WithShipping, WithDiscount {
        /*TODO: return some field*/
        @Override
        public String getZipCode() { return null; }

        /*TODO: return some field*/
        @Override
        public Optional<Integer> getShippingCosts() { return Optional.empty(); }

        /*TODO: return some field*/
        @Override
        public Optional<Integer> getDiscount() { return Optional.empty(); }
    }

/////////////////////////////////TAXCALCULATOR///////////////////////////////////////////

    static class TaxCalculator {
        public SalesTaxRates getSaleTaxes(String zipCode) {
            return getSaleTaxes(() -> zipCode);
        }

        public SalesTaxRates getSaleTaxes(WithZipCode zipCode) {
            return getSaleTaxesInternal((AllTaxRelatedInformation) zipCode::getZipCode);
        }

        public SalesTaxRates getSaleTaxes(ShoppingCart cart) {
            return getSaleTaxesInternal(new AllTaxRelatedInformation() {
                @Override
                public String getZipCode() { return cart.getZipCode(); }

                @Override
                public Optional<Integer> getShippingCosts() { return cart.getShippingCosts(); }
            });
        }

        public SalesTaxRates getSaleTaxes(Order order) {
            return getSaleTaxesInternal(order);
        }

        private Map<CacheKey, SalesTaxRates> cache = new HashMap<>();
        private <T extends WithZipCode & WithShipping & WithDiscount> SalesTaxRates getSaleTaxesInternal(T input) {
            SalesTaxRates cachedValue = cache.get(new CacheKey(input, input, input));
            if(cachedValue != null) {
                return cachedValue;
            }

            //TODO: calculation here
            return null;
        }

        private interface AllTaxRelatedInformation extends WithZipCode, WithShipping, WithDiscount {
            @Override
            default Optional<Integer> getShippingCosts() { return Optional.empty(); }
            @Override
            default Optional<Integer> getDiscount() { return Optional.empty(); }
        }

        private class CacheKey {
            private final WithZipCode zipCode;
            private final WithShipping shipping;
            private final WithDiscount discount;

            private CacheKey(WithZipCode zipCode, WithShipping shipping, WithDiscount discount) {
                this.zipCode = zipCode;
                this.shipping = shipping;
                this.discount = discount;
            }

            @Override
            public boolean equals(Object obj) {
                //TODO: implement equals by shipping cost, delivery and zipCode
                return false;
            }

            @Override
            public int hashCode() {
                //TODO: implement hashCode by shipping cost, delivery and zipCode
                return -1;
            }
        }
    }
}

(上面的代码是独立编译的,当你把它重构到你的代码库中时,去掉所有的static前缀)。

这是一个相当大的问题,但据我了解(在 cmets 中讨论后),您想要一种可扩展的方式来处理在计算中包括多个有时可选的值。上面的代码将所有不同的变体汇集到一个计算函数中,该函数必须处理某些值的存在或不存在(我标记了那些Optional)。您现在只需实现一次计算逻辑并拥有更好的缓存键。我省略了 ZipCodeOrderShoppingCart 的实现以缩短代码(使用某种字段应该很容易)。

希望我理解正确,如有任何误解或不清楚之处,请随时发表评论。

【讨论】:

    猜你喜欢
    • 2020-09-20
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    • 2011-08-04
    • 2014-04-19
    • 2018-05-17
    • 1970-01-01
    相关资源
    最近更新 更多