1

我有一个父类Product和两个子类:ToothbrushChainsaw. 它们的设置如下所示。

这是父类:

class Product {
    protected $productid;
    protected $type;

    public function __construct( $productid ) {
        $this->productid = $productid;
        // Performs a lookup in the database and then populates the $type property
    }
}

..这里是孩子们:

class Toothbrush extends Product {
    public function getPrice() {
        return 5; // returning an integer for simplicity; there's a calculation going on here
    }
}

class Chainsaw extends Product {
    public function getPrice() {
        return 1000; // in USD
    }
}

我想遍历$productid's 列表并获得项目的相应价格,无论它们是chainsaw's 还是toothbrushes。

问题(或者是吗?)

现在我一遍又一遍地听到父类不应该依赖子类来实现功能(是的,我读过这个问题,以及其他许多问题)。

这就是为什么我被引导认为我目前使用的解决方案(如下)不是最佳的:

class Product {
...
    public function getPrice() {
        switch($this->type) {
            case 'toothbrush':
                $theproduct=new Toothbrush($this->productid);
                return $theproduct->getPrice();
                break;
            case 'chainsaw':
                $theproduct=new Chainsaw($this->productid);
                return $theproduct->getPrice();
                break;
            }
        }
    }

我显然可以感觉到这里有些疏忽(想到当我得到 30 种不同的产品类型时会发生什么,我不寒而栗)。我已经阅读了有关抽象、接口和继承的内容,但不知道在这种情况下哪个可以工作。

谢谢!

编辑

看到很多答案,但还没有一个已经确定的答案。这是要点: 如果只有一个productid,我如何调用子方法?(在上述场景中,Product类在构造函数中从数据库中检索类型并相应地填充$type属性。

4

3 回答 3

2

现在我一遍又一遍地听到父类不应该依赖子类来实现功能

确切地。只要它在父级中定义为抽象方法并在子级中被覆盖,您switch将是一个不需要的依赖项,而调用不会。getPrice那么父类不需要知道具体的子类,仍然可以调用它们的方法。如果这对您来说听起来很奇怪,请阅读多态性,了解这个概念对于理解 OOP 很重要。

但你的问题更深:

这是要点:如果只有一个productid,我如何调用子方法?(在上述场景中,Product 类在构造函数中从数据库中检索类型并相应地填充 $type 属性。

显然,您永远不会创建 Chainsaw 或 Toothbrush 的实例。你不能创建一个产品,new Product然后告诉它“现在你是电锯”。对象的实际类型是不可变的。您试图通过在产品内部创建一个新的电锯来解决这个问题,该电锯应该是电锯,只是为了获得它的价格。这是非常错误的,我想你已经意识到了。

这就是为什么在评论中建议使用工厂模式。工厂是一个实例化对象并根据参数决定使用哪个子类型的类。它也是这样一个 switch 语句的有效位置。

例子:

class ProductFactory
{
    public function makeProduct($id)
    {
        $record = perform_your_database_lookup_here();

        switch ($record['type']) {
            case 'toothbrush':
                return new Toothbrush($id, $record);
            case 'chainsaw':
                return new Chainsaw($id, $record);
        }
    }
}

$factory = new ProductFactory();
$product = $factory->makeProduct(123);
echo $product->getPrice();

为简单起见,我将数据库查找放在工厂中。更好的解决方案是将它与两个类完全分开,例如在一个ProductTableGateway负责与产品表相关的所有数据库查询的类中。然后工厂只会收到结果。

顺便说一句,我还建议最终摆脱这些子类。没有严肃的在线商店对每种产品类型都有硬编码类,而是动态创建不同的属性集,并将不同的价格计算委托给其他类。但这是一个高深的话题,现在会走得太远。

于 2013-03-19T09:42:30.173 回答
0

您是正确的,父母不应该依赖他们的孩子,这就是您在父母中定义它的原因。

class Product {
    protected $productid;

    public function getPrice() {
        return NULL; // or 0 or whatever default you want
    }
}

class Toothbrush extends Product {
    public function getPrice() {
        return 5; // in USD
    }
}

class Chainsaw extends Product {
    public function getPrice() {
        return 1000; // in USD
    }
}

class Fake extends Product {
}

$f = new Fake();
var_dump($f->getPrice());

现在代码将始终有效,无论孩子是否定义 getPrice() 方法。

但也许最好使用类似的东西

class Product {
    protected $productid;
    protected $price;
    protected $type;

    public function __construct($id, $price, $type) {
      $this->productid = $id;
      $this->price = $price;
      $this->type = $type;
    }

    public function getPrice() {
        return $this->price;
    }
}

$tooth = new Product(1, 5, 'Toothbrush');
$chain = new Product(2, 1000, 'Chainsaw');
于 2013-03-19T08:37:28.053 回答
0

好吧,您可以阅读http://www.php.net/manual/en/language.oop5.abstract.php。此外,您可以在父级上执行此操作(以及获取它的方法),而不是在子级上声明价格,然后在需要时在任何子级中覆盖该方法。

于 2013-03-19T08:37:52.807 回答