I have a Task, Owner and Plan. Charge values are kept in a plan, owner is on a particular plan and task knows its owner.
A task needs to setup its charges based on the knowledge the owner has. Owner however needs to consult a plan to get those done. According to the Law of Demeter I am not allowed to do:
class Task {
/**
* @var Owner
*/
private $owner;
public function prepareCharges() {
$plan = $this->owner->getPlan();
$plan->chargeFor(...);
}
So I am wondering if this sort of routing trick is a viable solution.
class Task {
/**
* @var Owner
*/
private $owner;
public function prepareCharges() {
$this->owner->helpMeDoTheCharges($this);
}
public function doPlanCharges(Plan $plan) {
$distanceCharge = new Money();
$plan->chargeFor('distance', $distanceCharge);
$fixedCharge = new Money();
$plan->chargeFor('fixed', $fixedCharge);
}
}
Task basically tells its owner to push the plan to it so it can send it messages.
class Plan {
public function chargeFor($type, Money $money) {
}
}
class Owner {
private $plan;
public function helpTaskDoTheCharges(Task $task) {
$task->doPlanCharges($this->plan);
}
}
7
I would suggest that you do something like the following:
class Task {
public function prepareCharges() {
$charges = new Charges();
$distanceCharge = new Money();
$charges->add('distance', $distanceCharge);
$fixedCharge = new Money();
$charges->add('fixed', $fixedCharge);
$this->owner->charge($charges);
}
}
class Owner {
public function charge(Charges $charges) {
$this->plan->charge($charges);
}
}
Basically, instead of trying to awkwardly induce a situation where you have references to all those objects, put all of the charges inside a value object and pass it the owner.
The intention of the Law of Demeter is to minimize the knowledge that one part of your system has about another. Task
shouldn’t know about Plan
. Your approach of having Owner
call back into Task
with a Plan
may the follow the letter of the Law of Demeter, but it results in Task having in-depth knowledge of Owner
, precisely what Demeter is trying to avoid. You’d be better off returning the plan because that code was easier to follow.
Usually you’d just use a layer of indirection…
class Task {
private $owner;
public function prepareCharges() {
$this->owner->chargeFor(...);
}
...
class Owner {
public chargeFor() {
$plan = $this->getPlan();
$plan.chargeFor(...)
...
I think you should make the owner responsible for managing their charges, and simply pass the task to the owner. That way the task never has to touch the plan at all.
I don’t think your alternative is really that much better because a task still has to know about a plan and know that it’s responsible for determining the cost.
Here’s an example, I’m assuming the task is responsible for determining the cost variable (execution time), the plan is responsible for determining the rate per unit of cost variable, and the customer is responsible for accounting for the cost accrued over time:
class Task {
/**
* @var Owner
*/
private $owner;
public function execute() {
// Do whatever a task does
$this->owner->charge($this);
}
public function getExecutionTime() {
return 10; // Just an example
}
}
class Owner {
private $plan;
private $bill = 0;
public function charge(Task $task) {
$taskCost = $this->plan->calculateCost($task->getExecutionTime());
$bill += $taskCost;
}
}
class Plan {
private $rate = 0.5;
public function calculateCost($time) {
return $rate * $time;
}
}
If owners is responsible for invoking the task execution then you might be able to get away with not even passing the owner to the task at all, you could simply invoke the task and get the execution time from within the owner. That way the task wouldn’t depend on an owner to run, although if other aspects of your task depend on having an owner then that wouldn’t be practical.