vendredi 3 novembre 2017

How can I handle a shopping basket where there are a lot of products with potentially different cost calculation

I was doing an assignment and I had at least few products where I had a problem such as buy one get one free, buy 4 get one free and buy 5 and get 20 percent off. Now I was wondering what should I do to handle such a problem because as I encounter more products the code base for the product calculation will go out of hand. What is the best solution here ? Currently what I have done is to have a single class based on each product code and the calculation for the product is local to that file. The object construction is done via static factory method. Any suggestions with regards to the algorithm improvement and if I can further reduce the code and make it simple will be great.

class BOGOFCalculator implements BasketPriceCalculator
{
    const MIN_ITEMS_REQUIRED_FOR_DISCOUNT = 2;

    /**
     * @param Product $product
     * @param integer $totalItems
     * @return float
     */
    public function calculate(Product $product, $totalItems): float
    {
        return $this->calculateFullPrice($product, $totalItems) - $this->calculateDiscount($product, $totalItems);
    }

    /**
     * @param Product $product
     * @param $totalItems
     * @return float
     */
    private function calculateFullPrice(Product $product, $totalItems): float
    {
        return $product->getPrice() * $totalItems;
    }

    /**
     * @param Product $product
     * @param $totalItems
     * @return float
     */
    private function calculateDiscount(Product $product, $totalItems): float
    {
        return $product->getPrice() * floor($totalItems/static::MIN_ITEMS_REQUIRED_FOR_DISCOUNT);
    }
}

The basket looks like below

class Basket
{
    /**
     * @param Product[] $products
     *
     * @return float
     */
    public function calculateProductTotal(Product ...$products): float
    {
        $totalItemsOfEachProduct = $this->getTotalItemsOfEachProductInTheBasket(...$products);
        $items = $this->getDistinctProductsInTheBasketWithQuantity(...$products);
        $total = 0;
        foreach ($items as $productCode => $item) {
            /** @var BasketPriceCalculator $priceCalculator */
            $priceCalculator = PriceCalculatorFactory::getInstance($productCode);
            $total += $priceCalculator->calculate($item, $totalItemsOfEachProduct[$productCode]);
        }

        return $total;
    }

    /**
     * @param Product[] $products
     *
     * @return array
     */
    private function getTotalItemsOfEachProductInTheBasket(Product ...$products): array
    {
        $totalItemsPerProductCode = array_map(function ($product) { return $product->getCode(); }, $products);

        return array_count_values($totalItemsPerProductCode);
    }

    /**
     * @param Product[] $products
     *
     * @return array
     */
    private function getDistinctProductsInTheBasketWithQuantity(Product ...$products): array
    {
        $items = [];
        foreach ($products as $product) {

            if (!array_key_exists($product->getCode(), $items)) {
                $items[$product->getCode()] = $product;
            }
        }

        return $items;
    }
}

Aucun commentaire:

Enregistrer un commentaire