Search code examples
phpdesign-patternsarchitectureobserver-pattern

observer design pattern question


i am creating a oop system in php and would like to implement more observer patterns into it as i have heavy coupling between my classes that i wish to reduce.

my question is this. in relation to best practice in design for this pattern is it ok for one class to add an observer to another class, that class is working with. or should i keep the observer adding to the top most level of the chain?

example: (assume other methods called, but not included in the class, exist but are not important for this example.)

class orderItem extends observable {
     public function pick($qty, $user){
          $this->setUser($user);
          $position = new position($this->getPositionID());
          $position->addObserver(new ProductObserver());  // is this the best option ? ?
          $position->setQty($position->getQty() - $qty);
          $position->save();
          $this->notify(self::EVENT_PICK); // notify observers
     }
}

class orderProductObserver implements observer {
     public function update($orderitem){
           $position = new position($orderitem->getPositionID());
           $product = new product($position->getProductID());
           if($product->getQty() < $product->getMinimum()) {
                $alert = new minProductAlert($product);
           }
     }
}

class ProductObserver implements observer {
     public function update($position){
           $product = new product($position->getProductID());
           if($product->getQty() < $product->getMinimum()) {
                $alert = new minProductAlert($product);
           }
     }
}

$order = new orderItem(123);
$order->addObserver(new orderProductObserver()); // or is this the best option ??
$order->pick(2, 'bill');

Or alternatively if both methods are wrong i am very interested in your input.

would this example be the most ideal by removing dependency between orderitem and position ?

 class OrderItem extends Observable {
         public function pick($qty, $user){
              $this->setUser($user);
              $this->setPickedQty($qty);
              $this->save();
              $this->notify(self::EVENT_PICK); // notify observers
         }
    }

    class OrderItemPickObserver implements Observer {
         public function update($orderitem){
               $position = new Position($orderitem->getPositionID());
               $position->addObserver(new ProductPositionObserver());
               $position->setQty($position->getQty() - $orderItem->getPickedQty());
               $position->save();
         }
    }

    class ProductPositionObserver implements Observer {
         public function update($position){
               $product = new product($position->getProductID());
               if($product->getQty() < $product->getMinimum()) {
                    $alert = new minProductAlert($product);
               }
         }
    }
    $pickQty = 2;
    $orderitem = new OrderItem(123);
    $position = new Position($orderitem->getPositionID());
    if($position->getQty() >= $pickQty)
    {
           $orderitem->addObserver(new OrderItemPickObserver()); // or is this the best option ??
           $orderitem->pick($pickQty, 'bill');
    }

Solution

  • The second example looks good, but I'm not sure if creating new Position object inside update method of OrderItemPickObserver class. Instead, what I would suggest is to keep a Position object as a property of OrderItem class so that you can set it from outside.

    class OrderItem extends Observable {
             private $_position;
             public function setPosition($position){
                  $this->_position = $position;
             }  
             public function getPosition(){
                  return $this->_position;
             }  
        }
    

    Then update OrderItemPickObserver class:

    class OrderItemPickObserver implements Observer {
             public function update($orderitem){
                   $position = $orderitem->getPosition());
                   $position->setQty($position->getQty() - $orderItem->getPickedQty());
                   $position->save();
             }
        }
    

    And your calling code:

    $orderitem = new OrderItem(123);    
    $position = new Position();
    $position->addObserver(new ProductPositionObserver());
    $orderitem->setPosition($position);
    

    This way you can decouple OrderItemPickObserver and Position classes.

    EDITED:

    If your business logic doesn't allow you to have a Position object in OrderItem class, you can move the same to OrderItemPickObserver since this is the class which actually uses the Position object.

    class OrderItemPickObserver implements Observer {
                 private $_position;
                 function __construct($position){
                      $this->_position = $position;
                 }  
    
                 public function update($orderitem){
                       $position = $this->_position;
                       $position->setId($orderitem->getPositionID());
                       $position->setQty($position->getQty() - $orderItem->getPickedQty());
                       $position->save();
                 }
            }
    

    And your calling code:

    $orderitem = new OrderItem(123);    
    $position = new Position();
    $position->addObserver(new ProductPositionObserver());
    ...
    ...
    $orderitem->addObserver(new OrderItemPickObserver($position));