In order to try and explain my question about object-orientated design, I’ve created a couple of examples.
Let’s take a project with the following 3 classes:
- Customer
- Order
- Item
Then let’s say we want to do the following: For a customer with a specific email address, output the title and price of every item purchased across all orders.
I have some example code which would ultimately achieve the above. Before looking at it though, it’s worth noting that rather than using constructors, I’m using a build() method, and rather than taking discrete parameters, I’m taking an array of parameters. This is in order to accommodate the class’ functionality being easily accessible via HTTP (it allows a simple API controller to automatically route HTTP requests to the appropriate classes/methods and return a serialised result).
Client-side code example one:
$customer = new Customer;
$customer->build(array(
'email_address' => 'person@domain.com'
));
foreach ($customer->get_orders() as $order)
{
foreach($order->get_items() as $item)
{
echo('<strong>Item:</strong> '.$item->get_title().'<br /><strong>Price:</strong> '.$item->get_price().'<br />');
}
}
This example is the sort of code that got me excited about object-orientated programming when I first discovered it. The fact that you could type something as intuitive as $user->get_car()->get_manufacturer()->get_country()->get_international_dialing_code() to get the international dialing code for the founding country of the manufacturer of a user’s car (not an amazing example, but you get the idea)!
Here’s the code which the above would need to operate:
/**
* Customer class
*/
class Customer {
/**
* @var string $email_address The customer's email address.
*/
protected $email_address;
/**
* @var string $first_name The customer's first name.
*/
protected $first_name;
/**
* @var string $last_name The customer's last name.
*/
protected $last_name;
/**
* @var Order[] $orders An array of orders.
*/
protected $orders;
/**
* Build the object instance from an array of parameters.
*
* @param array $params An array of parameters.
*/
public function build(array $params)
{
if (array_key_exists('email_address', $params))
{
// Query the database and populate the user instance based off their email address...
}
else
{
throw new InvalidArgumentException('You can only build customers from email addresses at this time.');
}
}
/**
* Get all of the customer's orders.
*
* @return Order[] The customer's orders.
*/
public function get_orders()
{
// Query the database for the order information...
}
}
/**
* Order class
*/
class Order {
/**
* @var Item[] $items An array of items.
*/
protected $items;
/**
* @var DateTime $time The time the order was made.
*/
protected $time;
/**
* @var bool $paid Whether or not the order has been paid for.
*/
protected $paid;
/**
* Get all of the items in the order.
*
* @return Item[] The items in the order.
*/
public function get_items()
{
// Query the database for the item information...
}
}
/**
* Item class
*/
class Item {
/**
* @var string $title The title of the item.
*/
protected $title;
/**
* @var string $price The price of the item.
*/
protected $price;
/**
* Get the price of the item.
*
* @return string The price of the item.
*/
public function get_title()
{
return $this->title;
}
/**
* Get the price of the item.
*
* @return string The price of the item.
*/
public function get_price()
{
return $this->price;
}
}
As far as I can see though, this has a couple of problems, in that:
- The customer class needs to know about orders.
- The order class needs to know about items.
So, lets get onto an alternative…
Client-side code example two:
$customer = new Customer;
$customer->build(array(
'email_address' => 'person@domain.com'
));
$orders = new Orders;
$orders->get_all(array(
'customer_id' => $customer->get_id()
));
foreach ($orders as $order)
{
$items = new Items;
$items->get_all(array(
'order_id' => $order->get_id()
));
foreach($items as $item)
{
echo('<strong>Item:</strong> '.$item->get_title().'<br /><strong>Price:</strong> '.$item->get_price().'<br />');
}
}
Here’s the code which the above would need to operate:
/**
* Customer class
*/
class Customer {
/**
* @var string $email_address The customer's email address.
*/
protected $email_address;
/**
* @var string $first_name The customer's first name.
*/
protected $first_name;
/**
* @var string $last_name The customer's last name.
*/
protected $last_name;
/**
* @var Order[] $orders An array of orders.
*/
protected $orders;
/**
* Build the object instance from an array of parameters.
*
* @param array $params An array of parameters.
*/
public function build(array $params)
{
if (array_key_exists('email_address', $params))
{
// Query the database and populate the user instance based off their email address...
}
else
{
throw new InvalidArgumentException('You can only build customers from email addresses at this time.');
}
}
}
/**
* Orders class
*
* Builds orders.
*/
class Orders {
/**
* Fetches orders.
*
* @return Order[] An array of orders.
*/
public function get_all()
{
if (array_key_exists('customer_id', $params))
{
// Query the database for the order information...
}
else
{
throw new InvalidArgumentException('You can only build orders from customer IDs at this time.');
}
}
}
/**
* Order class
*/
class Order {
/**
* @var Item[] $items An array of items.
*/
protected $items;
/**
* @var DateTime $time The time the order was made.
*/
protected $time;
/**
* @var bool $paid Whether or not the order has been paid for.
*/
protected $paid;
}
/**
* Items class
*
* Builds items.
*/
class Items {
/**
* Fetches items.
*
* @return Item[] An array of items.
*/
public function get_all()
{
if (array_key_exists('order_id', $params))
{
// Query the database for the item information...
}
else
{
throw new InvalidArgumentException('You can only build items from order IDs at this time.');
}
}
}
/**
* Item class
*/
class Item {
/**
* @var string $title The title of the item.
*/
protected $title;
/**
* @var string $price The price of the item.
*/
protected $price;
/**
* Get the price of the item.
*
* @return string The price of the item.
*/
public function get_title()
{
return $this->title;
}
/**
* Get the price of the item.
*
* @return string The price of the item.
*/
public function get_price()
{
return $this->price;
}
}
The good news is:
- The classes are no longer dependent on other classes.
The bad news is:
- It’s far less intuitive, there’s more code, and prevents you being able to pass a single object (such as $order) into a view (whether or not that’s a good idea in itself is debatable, but let’s just say it’s fine) for easily building output.
My questions are, which of the above two scenarios showcases the best design? Would either of them be considered good design? Then finally, are there any other approaches that would be more suitable?
I look forward to hearing some thoughts