lundi 10 janvier 2022

Design pattern for share calculating order totals

I want to solve two problems:

  1. Share data between existing methods
  2. Less coupling this methods

What pattern I can use for calculating totals?

/** @var array $orderData */

// Ordered products total price
$orderedProductsTotal = $this->orderedProductsTotalPrice($orderData);

$total = $orderedProductsTotal;

// Shipping cost dependent on ordered products total price
$shippingCost = $this->shippingCost($orderedProductsTotal);
$total += $shippingCost;

// Client allowed credit, depends on ordered products total price
$total -= $this->credit($orderedProductsTotal);

// Available coupon depends on order data (i.e. special products in order, etc)
$total -= $this->coupon($orderData);

// Client personal discount applying on total amount, excluding shipping cost
$total -= $this->personalDiscount($total - $shippingCost);

I thinking about something like this

$calcs = [
    'OrderProductsCalc',
    'ShippingCalc',
    'CreaditCalc',
    'CouponCalc',
    'PersonalDiscountCalc',
];

$total = 0;


foreach ($calcs as $v) {
    // How to share data between calculators?
    // Separated calculated total values of each calculator
    // Order data
    // Something else
    /** @var CalculatorInterface $calculator */
    $calculator = $this->container->get($v);
    $total = $calculator->getTotal($total);
}

This is a simple class dependents on Cart items

class OrderProductsCalc implements CalcInterface
{
    private Cart $cart;

    public function __construct(Cart $cart)
    {
        $this->cart = $cart;
    }

    public function getTotal(float $total): float
    {
        $subTotal = $this->cart->getTotal();

        return $total + $subTotal;
    }
}

ShippingCalc is simple too as it run next to OrderProductsCalc.

But CreaditCalc::getTotal() should receive as $total the $subTotal inner value from OrderProductsCalc::getTotal().

class CreaditCalc implements CalcInterface
{
    private $customer;

    public function __construct(Customer $customer)
    {
        $this->customer = $customer;
    }

    /**
     * @var float $total The $subTotal inner value from OrderProductsCalc::getTotal()
     */
    public function getTotal(float $total): float
    {
        $balance = $this->customer->getBalance();
        if ($balance) {
            $credit = min($balance, $total);
            
            if ($credit > 0) {
                $total -= $credit;
            }
        }
        
        return $total;
    }
}

Aucun commentaire:

Enregistrer un commentaire