java 如何设计一个'捆绑'折扣超市结帐?

3htmauhk  于 2023-01-07  发布在  Java
关注(0)|答案(1)|浏览(136)

我正在做一个练习,设计一个超市,为产品提供各种折扣。折扣的例子有:

  • 购买x数量的物品可免费获得y
  • 以$y购买y数量的物品
  • 用$z购买物品x和物品y

我已经设法创建了前两个折扣。但由于折扣是根据 * 购物篮项目 * 计算的,我很难看到我如何才能使折扣适用于多个产品。我可以创建一个全新的项目,并标记为“x和y”,但这感觉有点像作弊。请提供建议

import java.math.BigDecimal;

public class Item {

    private final String sku;
    private final BigDecimal unitPrice;

    public Item(String sku, BigDecimal unitPrice) {
        this.sku = sku;
        this.unitPrice = unitPrice;
    }

    public BigDecimal getUnitPrice() {
        return unitPrice;
    }
}
import java.math.BigDecimal;

public class BasketItem {

    private final Item item;
    private int quantity;
    private final Discount discount;

    public BasketItem(Item item, Discount discount) {
        this.item = item;
        this.discount = discount;
        this.quantity = 1;
    }

    public BasketItem(Item item, Discount discount, int quantity) {
        this.item = item;
        this.discount = discount;
        this.quantity = quantity;
    }

    public void increment() {
        this.quantity++;
    }

    public BigDecimal calculateSubTotal() {
        return item.getUnitPrice().multiply(new BigDecimal(quantity));
    }

    public BigDecimal calculateDiscount() {
        return discount.apply(this);
    }

    public Item getItem() {
        return item;
    }

    public int getQuantity() {
        return quantity;
    }

}
import java.math.BigDecimal;

public interface Discount {

    BigDecimal apply(BasketItem item);

}
import java.math.BigDecimal;
import java.security.InvalidParameterException;

public class BuyXGetYFree implements Discount {

    private final int buy;
    private final int free;

    public BuyXGetYFree(int buy, int free) {
        if (buy < 1) {
            throw new InvalidParameterException("Can't buy less than 1 item");
        }

        if (free < 1) {
            throw new InvalidParameterException("Can't free less than 1 item for free");
        }

        if (free > buy) {
            throw new InvalidParameterException("The free amount must not exceed the purchased amount");
        }

        this.buy = buy;
        this.free = free;
    }

    @Override
    public BigDecimal apply(BasketItem basketItem) {

        int quantity = basketItem.getQuantity();
        BigDecimal unitPrice = basketItem.getItem().getUnitPrice();

        if (buy + free > quantity && quantity > buy) {
            return unitPrice.multiply(BigDecimal.valueOf(quantity - buy));
        }

        int multiplier = quantity / (buy + free);
        int forFree = multiplier * free;
        return unitPrice.multiply(BigDecimal.valueOf(forFree));
    }
    
}
import java.math.BigDecimal;

public class NoDiscount implements Discount {

    @Override
    public BigDecimal apply(BasketItem item) {
        return BigDecimal.ZERO;
    }
}
import java.math.BigDecimal;
import java.security.InvalidParameterException;

public class BuyXForY implements Discount{

    int buy;
    BigDecimal pay;

    public BuyXForY(int buy, BigDecimal pay) {
        if (buy < 1) {
            throw new InvalidParameterException("Buy must be at least 1");
        }
        this.buy = buy;
        this.pay = pay;
    }

    @Override
    public BigDecimal apply(BasketItem item) {
        int quantity = item.getQuantity();

        if (quantity < buy) {
            return BigDecimal.ZERO;
        }

        int multiplier = quantity / buy;
        int remainder = quantity % buy;

        BigDecimal buyAtDiscountedPrice = new BigDecimal(multiplier).multiply(pay);
        BigDecimal buyAtNormalPrice = new BigDecimal(multiplier).multiply(new BigDecimal(remainder));
        BigDecimal normalPrice = new BigDecimal(quantity).multiply(item.getItem().getUnitPrice());

        return normalPrice.subtract(buyAtDiscountedPrice.add(buyAtNormalPrice));
    }
}
public class DiscountAssignment {

    private final Discount discount;
    private final Item item;

    public DiscountAssignment(Discount discount, Item item) {
        this.discount = discount;
        this.item = item;
    }

    public Discount getDiscount() {
        return discount;
    }

    public Item getItem() {
        return item;
    }

}
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class Checkout {

    private final List<BasketItem> basketItems;
    private final List<DiscountAssignment> discountAssignments;

    public Checkout(List<DiscountAssignment> discountAssignments) {
        this.basketItems = new ArrayList<>();
        this.discountAssignments = discountAssignments;
    }

    public void scan(Item item) {
        Optional<BasketItem> basketItemOptional = basketItems.stream().filter(basketItem -> basketItem.getItem().equals(item)).findFirst();

        if (basketItemOptional.isPresent()) {
            basketItemOptional.get().increment();
            return;
        }

        Optional<DiscountAssignment> discountsToApply = discountAssignments.stream().filter(discountAssignment -> discountAssignment.getItem().equals(item)).findFirst();

        if (discountsToApply.isPresent()) {
            basketItems.add(new BasketItem(item, discountsToApply.get().getDiscount()));
        } else {
            basketItems.add(new BasketItem(item, new NoDiscount()));
        }
    }

    public BigDecimal total() {
        System.out.println(basketItems);

        return basketItems.stream()
                .map(BasketItem::calculateSubTotal)
                .reduce(BigDecimal.ZERO, BigDecimal::add)
                .subtract(basketItems.stream()
                        .map(BasketItem::calculateDiscount)
                        .reduce(BigDecimal.ZERO, BigDecimal::add));
    }

}

快速测试输出的总成本为4

import java.math.BigDecimal;
import java.util.List;

public class Test {

    public static void main(String[] args) {
        Item drink = new Item("Drink", new BigDecimal(1));
        Item candy = new Item("Candy", new BigDecimal(1));

        DiscountAssignment buyOneGetOneFreeDiscount = new DiscountAssignment(new BuyXGetYFree(1, 1), drink);
        DiscountAssignment buyTwoForOneDollar = new DiscountAssignment(new BuyXForY(2, BigDecimal.ONE), candy);

        Checkout checkout = new Checkout(List.of(buyOneGetOneFreeDiscount, buyTwoForOneDollar));
        checkout.scan(drink);
        checkout.scan(drink);
        checkout.scan(drink);
        checkout.scan(drink);
        checkout.scan(candy);
        checkout.scan(candy);

        System.out.println("Total cost: " + checkout.total());
    }

}

我尝试创建一个全新的捆绑项目。但每个项目必须单独扫描。

jmo0nnb3

jmo0nnb31#

我建议你尝试以下方法,而不是那些特殊的项目:

  • 提供折扣规则,就像你已经在做的那样。定义一个规则层次,因为你可能不想合并折扣(例如“买x,送y”和“买x,只付y”)。
  • 如果规则适用,则添加“折扣”项目,例如
  • “买x送y”-〉添加“free_items * item_price”的折扣,例如价格为4. 99的“买6送2”-〉值为“-9. 98”的位置
  • “以价格y购买x”-〉添加“项目 *(项目价格-折扣价格)”的折扣
  • “以价格z购买商品x并获得商品y”-〉为“y”添加折扣商品,其值为“amount_of_y *(price_of_y-z)”

最后,收据可能会如下所示(每一行都是一个项目):

Item          Amount Unit price  Total
======================================
Coke               4       0.99   3.96
Coke 4 for 3       1      -0.99  -0.99  //item added by the "buy 4 get 1 free" rule
Snickers           2       1.95   3.90
Snickers 1.70      2      -0.25  -0.50  //item added by the "get Snickers for 1.70" rule
Beer              11       1.50  16.50
Chips              1       2.69   2.69      
Chips (for 6 beer) 1      -2.69  -2.69  //item added by the "buy 6 beer and get 1 bag of chips" rule, applied once 1 bag of chips was in the cart  
======================================
Total                            22.87

这样做的好处是可以显示原始价格以及添加的折扣。当然,如果需要,元素的顺序可以不同,但我基本上会保持这样:

List<Item> basketItems; //items the customer added with the original price
List<Item> discountItems; //items created by rules to show the discounts that were applied

List<Item> finalItems; //a combination of basketItems and discountItems to show in the output - either just concatenate or sort by item, group, price etc. as needed

相关问题