关于php:从对象中删除太多的IF

Removing too many IF's from object

编辑:

我应该提到我希望它更面向对象。我不认为这里的代码在OO附近,也不使用开关,是吗?

OP:

首先,在下面的示例中,我使用的是荷兰语单位,因此计算看起来可能不正确,但您会明白这一点。

基本上,我有一份杂货店的产品清单。例如,在我的数据库中,我将价格存储在"按件价格"或"按磅价格"中。因此,为了计算每种产品的总价格,根据所选的数量,我正在处理下面的类。

例子:

在我的杂货店列表中,我有一些产品,在这个产品后面是一个文本字段和一个下拉列表。在文本字段中,我可以输入我想要的数量,并在下拉列表中选择是否需要盎司、磅等。根据这些值和我的数据库中的初始价格(每件价格等),我可以计算出每个产品的总价格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Calculation
{
    protected $price;
    protected $amount;
    protected $unit;

public function __construct($price, $amount, $unit)
{
    $this->price = $price;
    $this->amount = $amount;
    $this->unit = $unit;
}

public function calculate()
{
    if($this->unit === 'ounce')
    {
        return $this->formatOunce();
    }
    if($this->unit === 'pound')
    {
        return $this->formatPound();
    }
        return $this->formatOne();
}

public function formatOne()
{
    return $this->price * $this->amount / 100;
}

public function formatOunce()
{
    return $this->price / 1000 * $this->amount / 100;
}

public function formatPound()
{
    return $this->price / 1000 * 500 * $this->amount / 100;
}

}

我的问题是:

1
2
3
4
5
6
7
8
9
10
11
12
public function calculate()
{
    if($this->unit === 'ounce')
    {
        return $this->formatOunce();
    }
    if($this->unit === 'pound')
    {
        return $this->formatPound();
    }
        return $this->formatOne();
}

如何更改上述代码以使其成为良好的OO?我是使用存储库还是使用接口?或者我可以在这个特定的类中这样做来保持简单吗?我觉得有太多如果有的话。


我建议立即进行两次修改:

  • 使用类常量而不是硬编码的字符串标识符。其优点是双重的:IDES能够更好地支持自动完成,并且不再出现打字错误。

  • 使用switch()语句,使事情更清楚:

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Calculation
    {
        const UNIT_WEIGHT_OUNCE = 'ounce';
        const UNIT_WEIGHT_POUND = 'pound';
        // ...

        protected $price;
        protected $amount;
        protected $unit;
        // ...

        public function calculate()
        {
            switch ($this->unit) {
                case self::UNIT_WEIGHT_OUNCE:
                    return $this->formatOunce();
                case self::UNIT_WEIGHT_POUND:
                    return $this->formatPound();
                // ...
                default:
                    return $this->formatOne();
            }
        }

        // ...
    }

    这也将允许有一个"计算目录"和一个方法,而不是几个独立的方法来进行实际的计算,因为您可以将公式存储在一个数组甚至静态类中…

    最后一件更基本的事情:我想在这里讨论架构方法:

    为什么选择实现类Calculation?这对我来说是非常不直观的…实施像"定位购物车"这样的东西不是更自然吗?这样,这类对象就可以保存产品标识符、基价、数量/金额等?这将导致一个"购物车"类的自然方式…该类型的对象将保存第一个类型的对象列表。


    面向OO的方法(编辑问题后按要求):

    您将生成一个处理输出total()的基类。它调用执行计算的受保护方法。calculate()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Item
    {
        protected $price;
        protected $amount;

        public function __construct($price, $amount)
        {
            $this->price = $price;
            $this->amount = $amount;
        }

        protected function calculate()
        {
            return $this->price * $this->amount;
        }

        public function total($format = true)
        {
            if ($format) {
                return number_format($this->calculate(), 2);
            }
            return $this->calculate();
        }
    }

    现在,您可以使用该项的磅版本扩展基本项。pound版本将覆盖calculate()方法,因为计算方式不同。

    1
    2
    3
    4
    5
    6
    7
    class PoundItem extends Item
    {
        protected function calculate($format = true)
        {
            return $this->price / 1000 * 500 * $this->amount / 100;
        }
    }

    要生成对象,您需要一个构造函数方法或所谓的工厂来生成它们。这是工厂班。它也可以在您的basket类上实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class ItemFactory
    {
        static public function create($price, $amount, $type)
        {
            // this could be implemented in numerous ways
            // it could even just be method on your basket
            $class = $type ."Item";
            return new $class($price, $amount);
        }
    }

    创建磅类型的新项目:

    1
    $a = ItemFactory::create(49.99, 25,"Pound");

    由于PoundItem也是一个Item方法,您可以使用total()方法。但由于我们改变了calculate()的实现方式,现在它计算的是磅。

    1
    echo $a->total();


    你不想换成开关吗?

    首先,在概念上和逻辑上,切换是正确的方式。您正在使用一个变量$this->unit切换值。我觉得这很干净。[何时使用if else if else转换开关柜,反之亦然]

    其次,它更快[如果"else"比"switch()case"快吗?]虽然在你的情况下,可能没有那么重要。

    1
    2
    3
    4
    5
    6
    7
    8
    $res = NULL;
    switch($this->unit)
    {
        case 'ounce': $res = $this->formatOunce(); break;
        case 'pound': $res = $this->formatPound(); break;
        default: $res = $this->formatOne();
    }
    return $res;


    你的问题是用多态性替换条件,这样你就从每个条件中分离出代码,并把它们放到自己的类中:盎司类知道如何计算盎司,磅类知道如何计算磅。

    如前所述,你仍然需要某种工厂来决定是时候使用磅还是盎司。那家工厂看起来…嗯,就像你现在的样子(但很可能是从别处抽象出来的)。

    由于目前的代码有多简单,我认为这些更改中的任何一个都将被重构得太早。你现在所拥有的并不复杂——理解代码的作用不会让这里的人放慢脚步。一旦你开始有更复杂的方法来计算某个东西,那么观察多态性将是更好的选择。

    同样,我认为switch语句vs if条件建议也不是很有用的建议。在这种情况下,它们不会为可读性添加任何内容。