Question

I have a parent class Product and two child classes: Toothbrush and Chainsaw. They are set-up as shown below.

Here's the parent class:

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
    }
}

.. and here are the children:

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
    }
}

I want to iterate through a list of $productid's and get the corresponding price of the items regardless of whether they are chainsaw's or toothbrushes.

The problem (or is it?)

Now I've heard over and over again that a parent class should NOT rely on the child class to implement functionality (yes I read this question, along with many others).

Which is why I'm led to think that the solution I'm currently using (below) isn't optimal:

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;
            }
        }
    }

I can obviously sense that something is remiss here (I shudder to think of what would happen when I get 30 different product types). I've read about abstraction, interfaces, and inheritance and can't figure out which one would work in this scenario.

Thank you!

EDIT

Seeing alot of answers, but none that've nailed it just yet. Here is the main point: How do I call a child method if have only a productid? (In the above scenario, the Product class retrieves the type from the database in the constructor and populates the $type property accordingly.

Was it helpful?

Solution

Now I've heard over and over again that a parent class should NOT rely on the child class to implement functionality

Exactly. And your switch would be such an unwanted dependency, while a call to getPrice would not, as long as it's defined as an abstract method in the parent and overridden in the children. Then the parent class does not need to know the concrete child classes and still can call their methods. If that sounds strange to you, read about polymorphism, it's important to understand this concept to understand OOP.

But your problem goes deeper:

Here is the main point: How do I call a child method if have only a productid? (In the above scenario, the Product class retrieves the type from the database in the constructor and populates the $type property accordingly.

Obviously you don't ever create instances of Chainsaw or Toothbrush. You can't create a product with new Product and afterwards tell it "now you are a chainsaw". The actual type of an object is immutable. You tried to get around that with creating a new chainsaw inside of the product that was supposed to be a chainsaw just to have access to its price. This is horribly wrong and I think you already realized it.

This is why the factory pattern was suggested in the comments. A factory is a class that instantiates objects and decides which subtype is used based on parameters. Also it is a valid place for such a switch statement.

Example:

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();

For simplicity I put the database lookup in the factory. A better solution would be to have it completely separated from both classes, for example in a ProductTableGateway class that would be responsible for all database queries related to the products table. The factory then would only recieve the result.

By the way, I'd also recommend to get rid of these subclasses in the end. No serious online shop has hard coded classes for each product type, instead different attribute sets are created dynamically and different price calculations are delegated to other classes. But this is an advanced topic, which would go too far now.

OTHER TIPS

Well, you could read http://www.php.net/manual/en/language.oop5.abstract.php. Also, instead of declaring the price on the children you could do it (along with the method to get it) on the parent and then override that method in any children if you need to.

You are correct that a parent should not rely on their children, thats why you define it in the parent aswell.

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());

Now the code will always work, regardless if the children define the getPrice() method.

But perhaps its better to use something like

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');
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top