3

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

}

Simple diagram for the problem

7
  • 1
    Do you have to keep the charge values in a plan or can you be flexible with moving it around? I assume it is fixed but since nothing was mentioned about it I thought I clarify so I don't make assumptions in my answer. Thank you.
    – AvetisG
    Mar 9 '15 at 0:42
  • 1
    Charges are plan specific Mar 9 '15 at 0:59
  • 2
    I should have figured that was the case. I posted a diagram on my edit just to make it more explicit.
    – AvetisG
    Mar 9 '15 at 1:05
  • Most of the time changing composition is the answer. You're breaking LoD not because your case is special, but because your high level objects try to controll too much (go into details).
    – shudder
    Mar 27 '15 at 13:37
  • @shudder I dont get it Mar 27 '15 at 16:51
0

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.

3

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(...)
        ...
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.