Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic / Plugable PHP Classes

I have a question which is sonewhat more of a design question. I have a class which defines a bunch of functions. However, I want that the behaviour of some functions are changeable during runtime - or at least working like a plugin interface during design time. eg:

class MyClass {  
  public function doSomething();
}

$obj = new MyClass();
// PSEUDO CODE
$obj->doSomething = doSomethingElse;
$obj->doSomething(); // does something else

I had various Ideas how to implement something like this in "real" code. However, I am not quite sure, which is the right way to go. I first thought I could use an Interface for this purpose.

interface IDoSomethingInterface {  
  public function doSomething();
}

class DoSomethingClass implements IDoSomethingInterface
{
  public function doSomething()
  {
    // I do something!
  }
}

class DoSomethingElseClass implements IDoSomethingInterface
{
  public function doSomething()
  {
    // I actually do something else!
  }
}

class MyClass {
  public doSomething(IDoSomething $doSomethingProvider)
  {
    $doSomethingProvider->doSomething();
  }
}

$obj = new MyClass();
$doSomethingProvider = new DoSomethingClass();
$doSomethingElseProvider = new DoSomethingElseClass();

$obj->doSomething($doSomethingProvider); // I do something!
$obj->doSomething($doSomethingElseProvider); // I do something else!

This approach could be expanded to not have the doSomething provider passed as a parameter, but to set it either as an object member or even class member. However, I did not like that I have to create an instance of n class which does not even contain a single member variable and the only method could easily be a static class function. There's just no need for an object at that point imo. I then was going to try to use function variables - but I'd fall out of OOP then, which I didn't like either. My questions are: which is the best way to implement a system like I described? Which approach would you try or use? Is there anything obvious I might not have thought of? Or should I just go with the interface approach and my bad feeling about instanciating 'bodyless' objects is just nerd preciousness?! I'm curious about your answers!

Edit:

Because I'm really interested in clarifying if this actually is (or should be) a State or a Strategy Pattern, I'll go into some more detail on the implementation. I have a base class of collections from which I derive different specific implementations. I want to be able to select a single element from any specific implementation - however, I want to be able dynamically alter how this element is selected from the collection, eg:

class MyCollection implements Countable, ArrayAcces, Iterator
{
  // Collection Implementation   
  public function select(ISelectionStrategy $strategy)
  {
    return $strategy->select($this);
  }   
} 

interface ISelectionStrategy
{
  public function select(MyCollection $collection);
}  

class AlphaSelectionStrategy implements ISelectionStrategy
{
  public function select(MyCollection $collection);
  {
    reset($collection);
    if (current($collection))
      return current($collection);
    else
      return null;
  }
}

class OmegaSelectionStrategy implements ISelectionStrategy
{
  public function select(MyCollection $collection)
  {
    end($collection);
    if (current($collection))
      return current($collection)
    else
      return null;
  }
}

class RandomSelectionStrategy implements ISelectionStrategy
{
  public function select(MyCollection $collection)
  {
    if (!count($collection))
      return null;
    $rand = rand(0, count($collection)-1);
    reset($collection);
    while($rand-- > 0)
    {
      next($collection);
    }
    return current($collection);
  }
}

$collection = new MyCollection();
$randomStrategy = new RandomSelectionStrategy();
$alphaStrategy = new AlphaSelectionStrategy();
$omegaStrategy = new OmegaSelectionStrategy();

$collection->select($alphaStrategy); // return first element, or null if none
$collection->select($omegaStrategy); // return last element, or null if none
$collection->select($randomStrategy); // return random element, or null if none 

This is basicly what I want to achieve. Is this now more a strategy pattern implementation or the state pattern - albeit I used the term strategy because it fits more in this case anyway. As far as I understand the strategy and state pattern a basicly the same, except their intent is different. The link provided by Gordon states that a state pattern's intent is "Allow an object to alter its behavior when its internal state changes" - but this it not the case here. What I want is to be able to tell my MyCollection class "use this or that algorithm to give me an element" - not "give me an element using an algorithm that you determine through your own state". Hope someone can clarify this!

Best regards, Daniel

like image 342
Daniel Baulig Avatar asked Jan 31 '10 11:01

Daniel Baulig


1 Answers

Your approach is correct. It is a Strategy Pattern (UML diagram):

Check out my answer to this question (skip to where it says Since you want to dynamically change behavior):

  • Can I include code into a PHP class?

An alternative to your specific UseCase would be to capsule the Selection Strategies into a single Service class, instead of assigning them to your Collection class. Then instead of passing the strategies to the collection, pass the collection to the Service Class, e.g.

class CollectionService implements ICollectionSelectionService
{
    public static function select(MyCollection $collection, $strategy) {
        if(class_exists($strategy)) {
            $strategy = new $strategy;
            return $strategy->select($collection);
        } else { 
            throw new InvalidArgumentException("Strategy does not exist");
        }    
    }
}
$element = CollectionService::select($myCollection, 'Alpha');

I leave it up to you whether to use this with static methods or not. To prevent multiple instantiation of Selection strategies, you could store the Selection objects inside the Service for reuse.

For other Behavioral Patterns check

  • http://sourcemaking.com/behavioral_patterns
  • http://www.fluffycat.com/PHP-Design-Patterns
  • http://www.phppatterns.com/docs/?idx=design
like image 90
Gordon Avatar answered Oct 08 '22 03:10

Gordon