开发者

A good design pattern for conditional statements based on the status of multiple variables

开发者 https://www.devze.com 2023-04-04 11:55 出处:网络
I\'m working out a method to calculate a total for a shopping cart written in PHP and would like some feedback on a good design pattern for handling the different conditions. I am trying to offer the

I'm working out a method to calculate a total for a shopping cart written in PHP and would like some feedback on a good design pattern for handling the different conditions. I am trying to offer the admins multiple strategies for calculating discounts. Admins will be given the choice to apply discounts before or after taxes are applied, as well as apply the discount to the shipping or not. This gives an overview of my task.

The Variables

I have the following variables for this task with the possible values:

$tax_option : 'before', 'after'

$shipping_option : 'yes', 'no'

In addition to these two variables, the formula for calculating the total value will change depending on the relationship between $subtotal (the amount of the items in the cart) and the $reduction (the total amount to be discounted).

Generally speaking, my logic is that I test for each of the 4 combinations of the $tax_option and $shipping_option variables. I also need to change the formula for situations where the $subtotal is less than or equal to the $reduction. In all, I have 8 different conditions.

The Possibilities

I figure that I really have 3 different options here: if statements, a switch statement, or a strategy pattern. I'll show the structure of the if statement and the strategy pattern, but will exclude the switch possibility because that just does not seem right in this context.

if Statements

Here is the general pattern of the if statement that I am considering. Note that I know I could restructure this slightly, but for whatever reason this is more readable for me.

if($tax_option == 'after' && $shipping_option == 'yes')
{
    if($subtotal <= $reduction)
    {

    }
    else
    {

    }
}
elseif($tax_option == 'before' && $shipping_option == 'yes')
{
    if($subtotal <= $reduction)
    {

    }
    else
    {

    }
}
elseif($tax_option == 'before' && $shipping_option == 'no')
{
    if($subtotal <= $reduction)
    {

    }
    else
    {

    }
}
elseif($tax_option == 'after' && $shipping_option == 'no')
{
    if($subtotal <= $reduction)
    {

    }
    else
    {

    }
}
else
    $new_total = $total;

Strategy Pattern

After looking around online for a solution to this problem, I learned about the Strategy Pattern, which looks awesome. I started work on it here and wouldn't mind some feedback on it. So far it looks like the following code, with obvious deletion of some of the routines.

class DPPCalculateTotal
{
    protected $formulas = array();

    public function DPPCalculateTotal($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {
        foreach($this->formulas as $formula)
        {
            if($formula->test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction))
            {
开发者_高级运维                return $formula->calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction);
            }
        }
    }

    function add_formula(DPPFormula $formula)
    {
        $this->formulas = $formula;
    }
}

interface DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction);

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction);
}

class AfterTaxesYesShippingGreaterSubotal implements DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }
}

class AfterTaxesYesShippingLesserSubotal implements DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }
}

class AfterTaxesNoShippingGreaterSubotal implements DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }
}

class AfterTaxesNoShippingLesserSubotal implements DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }
}

class BeforeTaxesYesShippingGreaterSubotal implements DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }
}

class BeforeTaxesYesShippingLesserSubotal implements DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }
}

class BeforeTaxesNoShippingGreaterSubotal implements DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }
}

class BeforeTaxesNoShippingLesserSubotal implements DPPFormula
{
    public function test($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }

    public function calculate_total($tax_option, $shipping_option, $total, $subtotal, $shipping, $tax, $coupons_amount, $reduction)
    {

    }
}

The Questions

1) In your opinion, what would be the best way to proceed here?

2) What are the advantages of the Strategy Pattern in this instance?

3) As this is the first time I'm trying out the Strategy Pattern, does it look like I'm heading in the right direction?

Thank you so much for your input! I'm having fun learning this pattern and will appreciate any feedback I can get!


I think you're heading the right way. With the strategy pattern it's really easy to add new strategies later on as the application gets bigger or the requirements change (which in my experience is the case 9 times out of 10).

What I would suggest is making one more class called something like Order to encapsulate all your order details in there and pass the objects around instead. Your tests and calculation methods would be a bit tidier

interface DPPFormula
{
    public function test(OrderInteface $order);

    public function calculate_total(OrderInterface $order);
}

interface OrderInterface
{
    function setTotal($total);
    function getTotal();
}

You could then do something similar to

$order->setTotal($calculator->DPPCalculateTotal());

Depending on your complexity you may or may not want to use an interface for the Order. I strongly suggest you do use one as this further increases the abstraction.


I think it would be possible to split the total up into a discountable amount and a not discountable amount. You then apply the discount to the discountable amount, and add on the rest, something like this pseudocode:

extra = 0
if (tax_option)
  subtotal += tax(subtotal)
else
  extra += tax(subtotal)
if (delivery_option)
  subtotal += delivery
else
  extra += delivery
if (subtotal > reduction)
  subtotal -= reduction
else
  // stuff here
subtotal += extra


Have you thought about using a lookup table/array? Might make things a little clearer to someone else. The array key could be the string for the various combinations, then you can associate any values you want to the lookup keys.

$lookup = array(
    'taxo-after:shipo-yes'=> array('reduction'=>100),
    'taxo-after:shipo-no'=> array('reduction'=>100),
    'taxo-before:shipo-yes'=> array('reduction'=>100),
    'taxo-before:shipo-no'=> array('reduction'=>100),
    ...
 );

 $lookup_key = 'taxo-'.$tax_option.':'.'shipo-'.$shipping_option;
 if ( $subtotal < $lookup[$lookup_key]['reduction'] ) {
 } else {
 }

That pretty much replaces your entire first code example. 1 array declaration and 1 if statement. You can add a hundred possibilities without any performance penalty or much more code.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号