2
\$\begingroup\$

Note

This question was born in request to post more details from this question on Programmers. Since details may uncover new/different complexity, I'm asking another question here. I also will use this question for reference purposes as it more fully defines my application.

Currently I am seeking to improve

  • storing/retrieving pricing information for a specific product
  • computing pricing information for a specific product

Right now the above information is retrieved and computed via if/then/else statement blocks.

CODE

class Price
{

    public $product;

    public $base;

    public $assembly;

    public $discount;

    public $total;

    public $motorPart1;

    public $motorOption1;

    public $pumpPart2;

    public $pumpPart3;

    public $pumpOption2;

    public $pumpOption3;

    function load()
    {
        if ($this->product == "MOTOR")
        {
            $this->base = 200;
            $this->motorPart1 = 90;
            $this->motorOption1 = 21;
        }
        elseif ($this->product == "PUMP")
        {
            $this->base = 500;
            $this->pumpPart2 = 70;
            $this->pumpPart3 = 110;
            $this->pumpOption2 = 13;
            $this->pumpOption3 = 19;
        }
    }
}

class Calc
{

    public $price;

    public $spec;

    public $option;

    function __construct()
    {
        $this->spec = new Spec();
        $this->price = new Price();
        $this->option = new Option();
    }

    function calcPrice()
    {
        $this->spec->load();
        if ($this->spec->product == "MOTOR")
        {
            $this->price->assembly = $this->price->base
                + $this->price->motorPart1
                * $this->spec->motorPart1Count;

            $this->price->total = $this->price->assembly * 1.1;
            if ($this->option->motorOption1)
                $this->price->total += $this->price->motorOption1;

            $this->price->discount = $this->price->total * 0.05;
            $this->price->total -= $this->price->discount;
        }
        elseif ($this->spec->product == "PUMP")
        {
            $this->price->assembly = $this->price->base
                + $this->price->pumpPart2 
                * $this->spec->pumpPart2Count 
                + $this->price->pumpPart3 
                * $this->spec->pumpPart3Count;

            $this->price->total = $this->price->assembly * 1.1;
            if ($this->option->pumpOption2)
                $this->price->total += $this->price->pumpOption2;

            if ($this->option->pumpOption3)
                $this->price->total += $this->price->pumpOption3;

            $this->price->discount = $this->price->total * 0.05;
            $this->price->total -= $this->price->discount;
        }
    }
}

class Option
{

    public $motorOption1;

    public $pumpOption2;

    public $pumpOption3;
}

class Spec
{

    public $product;

    public $motorPart1Count;

    public $pumpPart2Count;

    public $pumpPart3Count;

    function load()
    {
        if ($this->product == "MOTOR")
        {
            $this->motorPart1Count = 13;
        }
        elseif ($this->product == "PUMP")
        {
            $this->pumpPart2Count = 19;
            $this->pumpPart3Count = 21;
        }
    }
}

class MotorController
{

    private $calc;

    function __construct()
    {
        $this->calc = new Calc();
    }

    function controllerAction()
    {
        $this->calc->price->product = "MOTOR";
        $this->calc->spec->product = "MOTOR";

        // option data is specified through HTTP POST (hardcoded here)
        $this->calc->option->motorOption1 = true;

        $this->calc->price->load();
        $this->calc->calcPrice();

        print "MOTOR:" . PHP_EOL;
        print "Motor Total:\t" . $this->calc->price->total . PHP_EOL;
        print "Discount:\t" . $this->calc->price->discount . PHP_EOL;
        if ($this->calc->option->motorOption1)
            print "Option 1 cost:\t" . $this->calc->price->motorOption1 . PHP_EOL;
        print "-----------------------" . PHP_EOL;
    }
}

class PumpController
{

    private $calc;

    function __construct()
    {
        $this->calc = new Calc();
    }

    function controllerAction()
    {
        $this->calc->price->product = "PUMP";
        $this->calc->spec->product = "PUMP";

        // option data is specified through HTTP POST (hardcoded here)
        $this->calc->option->pumpOption2 = true;
        $this->calc->option->pumpOption3 = false;

        $this->calc->price->load();
        $this->calc->calcPrice();

        print "PUMP:" . PHP_EOL;
        print "Pump Total:\t" . $this->calc->price->total . PHP_EOL;
        print "Discount:\t" . $this->calc->price->discount . PHP_EOL;
        if ($this->calc->option->pumpOption2)
            print "Option 2 cost:\t" . $this->calc->price->pumpOption2 . PHP_EOL;
        if ($this->calc->option->pumpOption3)
            print "Option 3 cost:\t" . $this->calc->price->pumpOption3 . PHP_EOL;
        print "-----------------------" . PHP_EOL;
    }
}

// Example of Motor Controller use:
$motorController = new MotorController();
$motorController->controllerAction();

// Example of Pump Controller use
$pumpController = new PumpController();
$pumpController->controllerAction();

Above code compiles and runs. It fairly accurately represents the real-world application scenario that I am working on.

Questions

Application/Example Differences

User uses HTML form, chooses product, chooses options, then submits. Appropriate Product Controller picks up from there, does some processing, then sends results to the view. In my example above I chose to hardcode HTML form selections which are normally made by the user. I've also chosen to hardcode corresponding View output. The hardcoded code is in the appropriate Controllers.

Simplifications:

Example code contains two types -- "Pump" and "Motor". Actual application contains about 6 different types of Pumps with similar and differing characteristics, and a couple of business-specific non-pump object, which I chose to be represented as a Motor. More Pump and non-pump types to be expected in the real app, as application develops.

Use Case (using Pump)

User picks Pump Product type (appropriate Controller), selects available Pump option #3 out of the options available, submits. User gets resulting Pricing for the chosen option.

What am I looking for, aka "My Question"

Ideally I am seeking for ways to do step-wise refactoring. A single-step total rewire of the application is prohibitive even if possible, simply due to the complexity/verbosity of the app and the amounts of tests to fix while rewriting. Nevertheless I am seeking a final destination:

  • a set of Design Patterns that will make the if/then/else mess go away
  • make the code more maintainable.
  • reduce complexity
\$\endgroup\$
  • 1
    \$\begingroup\$ What you're wanting is polymorphism. Both "pump" and "motor" are "products" which have a shared functionality for getting their price. You could construct a class for each "product" and when you're getting the prices, iterate over the products and call the shared "getPrice" function. \$\endgroup\$ – Kyle Falconer Nov 18 '15 at 22:33
  • \$\begingroup\$ Polymorphism is good for this. However, in this case the shared getPrice function will keep if/then/else blocks for each product type, correct? It will also need to store every property related to each product type and corresponding algorithm to compute price for each product type. That can be a bit messy and long. \$\endgroup\$ – Dennis Nov 18 '15 at 23:05
  • 1
    \$\begingroup\$ No, you would simply iterate over the products, and for each product call the getPrice function. Because of polymorphism, if they share the same interface (for the shared function getPrice), the product type will be looked up at run time in order to call the appropriate function. I'm not familiar enough with PHP to code a solution for you, but can demonstrate this in Java if you like. \$\endgroup\$ – Kyle Falconer Nov 19 '15 at 0:06
  • \$\begingroup\$ There's also a good article which shows how to do this in PHP, and the reasoning for it: code.tutsplus.com/tutorials/… \$\endgroup\$ – Kyle Falconer Nov 19 '15 at 0:08
  • 1
    \$\begingroup\$ Yes. Each "product" would define its own getPrice \$\endgroup\$ – Kyle Falconer Nov 19 '15 at 0:09
2
\$\begingroup\$

Ideally I am seeking for ways to do step-wise refactoring.

You need a better design to refactor to. Think about how motors, pumps, etc. are all the same.


  • make the code more maintainable.
  • reduce complexity

You should revisit your basic design.

Product class thoughts

  • Everything is a product
  • every product has a price. A "base price"?
  • Any thing that is independently priced will be a product
  • A product may have components - a set of Product objects
  • A product may have options - a set of... Option objects, I guess.
  • Options don't seem to be Products.
  • A product's price may be a base price + sum of options prices + the sum of sub-product prices.
  • Not sure what an assembly is. All the above seems to capture the idea of "assembly"

This could be the basis that get to where you want to go with the code. As pump, motor, etc. will subclass Product you have the polymorphic framework you need.


Calculating Price Polymorphically

Each sub-class will calculate it's own price. So far it seems that a "Price" function is sufficient. It will be as complex as needed to calculate its price in combination to its sub-parts.

Client code will only do one thing. Call "Price" - and that thing calculate's its own price. In turn, that thing calls "Price" on each sub-part.

It does not matter that a pump consists of motors or motors consist of pumps or that anything consists of anything else. Because every thing is a Product and every product has the same structure and the price is always calculated the same way: BasePrice + sum of component prices + sum of options. The client does not know, or care, what the product consists of - just give me the price. This is polymorphism.


  • a set of Design Patterns that will make the if/then/else mess go away

A good, simple, common data structure - the Product class as discussed - will make this happen.


You should read Head First Object-Oriented Analysis and Design.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.