5 PHP Patterns im Schnelldurchlauf: Factory, Iterator, Observer, Singleton, Strategy

Dieser Post versteht sich ein bisschen als „Reminder“. Bei den folgenden 5 Patterns habe ich in den Codebeispielen besonderen Wert auf Einfachheit gelegt, sodass man ohne große Erklärung gleich sieht, worum es geht. Ich finde es wichtig, sich nach einiger Zeit immer mal wieder die Patterns vor Augen zu halten.

Factory

Eine „Fabrik“ erzeugt je nach Anwendungskontext verschiedene Klassen und gibt diese zurück.

interface IPermission
{
	public function deleteFilePermission();
}

class AdminPermission implements IPermission
{
	public function deleteFilePermission()
	{
		return true;
	}
}

class UserPermission implements IPermission
{
	public function deleteFilePermission()
	{
		return false;
	}
}

//-----------------------------------------------

class UserFactory
{
	public static function build($type)
	{
		switch ($type)
		{
			case 'admin': 
				return new AdminPermission();
				break;
			case 'user': 
				return new UserPermission();
				break;
			default: 
				throw new Exception("No valid type");
		}			
	}
}

//-----------------------------------------------

//admin is allowed to delete files
$admin=UserFactory::build("admin");
var_dump($admin->deleteFilePermission()); //bool(true)

//user is not allowed to delete files
$user=UserFactory::build("user");
var_dump($user->deleteFilePermission()); //bool(false)	

Iterator

Das iterieren mit foreach ist durch das Iterator-Pattern auch über eine Klasse möglich – so als ob sie ein Array wäre. Sinnvoll, wenn ich klassenintern komplexe Strukturen abgebildet habe, die ich nach außen simpel darstellen möchte. Allerdings ist mein Beispiel bewusst einfach gehalten – eigentlich eine Vergewaltigung des iterator-Patterns, bei dem ein einfaches Array die bessere Wahl gewesen wäre. Aber der Zusammenhang sollte klar werden.

class LottoNumbers implements Iterator 
{
	private $_lottoNumbers;
	private $_currentPos = 0;
 
	public function __construct(array $lottoNumbers) 
	{
		$this->_lottoNumbers = $lottoNumbers;
	}
 
	public function current() 
	{
		return $this->_lottoNumbers[$this->_currentPos];
	}
 
	public function key() 
	{
		return $this->_currentPos;
	}
 
	public function next() 
	{
		++$this->_currentPos;
	}
 
	public function rewind() 
	{
		$this->_currentPos=0;
	}
 
	public function valid() 
	{
		return isset($this->_lottoNumbers[$this->_currentPos]);
	}
}

$lotto = new LottoNumbers(array(12,41,14,34,3,23));

foreach ($lotto as $nr => $lottoNumber) 
{
	echo "Lottonr.".$nr." = ".$lottoNumber."<br />";
}

Dabei ist das iterator-Interface selbst „php-intern“ wie folgt definiert:

interface Iterator
{
	abstract public mixed current ( void )
	abstract public scalar key ( void )
	abstract public void next ( void )
	abstract public void rewind ( void )
	abstract public boolean valid ( void )
}

Observer

Eines der klassischsten Patterns überhaupt. Wird verwendet, um das Polling obsolet zu machen. Wenn es mehrere Beobachter gibt, die sich für den Status eines Subjekts interessieren ist es doch viel intelligenter, dass das Subjekt die Beobachter selbst benachrichtigt wenn sich etwas tut. Im Polling-Verfahren würden die Beobachter ständig anfragen müssen, ob es denn was neues gibt. Das Observer-Verfahren spart Rechenzeit und macht den Code sauberer.

interface Subject
{
	public function addObserver(Observer $Observer);
	public function removeObserver(Observer $Observer);
	public function notify();
}

class Train implements Subject
{
	protected $_minutesToArrival;
	protected $_observers = array();
 
	public function __construct($mins)
	{
		$this->setMinutesToArrival($mins);
	}
 
	public function addObserver(Observer $observer)
	{
		$this->_observers[] = $observer;
	}
 
	public function removeObserver(Observer $observer)
	{
		for ($i = 0; $i < count($this->_observers); $i++)
		{
			if ($this->_observers[$i] === $observer)
			{
				unset($this->_observers[$i]);
				break;
			}
		}
	}
 
	public function notify()
	{
		foreach($this->_observers as $o)
			$o->update($this);
	}
 
	public function setMinutesToArrival($mins)
	{
		$this->_minutesToArrival = $mins;
		$this->notify();
	}
 
	public function getMinutesToArrival()
	{
		return $this->_minutesToArrival;
	}
}

//----------------------------------------------

interface Observer
{
	public function update(Subject $Subject);
}

class PassengerJohn implements Observer
{
	public function update(Subject $train)
	{
		echo "John is glad that he arrives in ".$train->getMinutesToArrival()."mins<br />";
	}
}

class PassengerMary implements Observer
{
	public function update(Subject $train)
	{
		echo "Mary can sleep ".$train->getMinutesToArrival()."mins till she arrives<br />";
	}
}


//----------------------------------------------

$train = new Train(60);
$john = new PassengerJohn();
$train->addObserver($john);
$mary = new PassengerMary();
$train->addObserver($mary);
$train->setMinutesToArrival(55);
//John is glad that he arrives in 55mins
//Mary can sleep 55mins till she arrives
$train->setMinutesToArrival(45);
//John is glad that he arrives in 45mins
//Mary can sleep 45mins till she arrives
$train->removeObserver($mary);
$train->setMinutesToArrival(35);
//John is glad that he arrives in 35mins

Singleton

Der Klassiker für Logger- oder Datenbank-Klassen, von denen es naturgemäß sinnvollerweise nur eine Instanz geben sollte. Allerdings sollte man sich in Verbindung dazu auch die Kritik am Singleton-Pattern durchlesen.

class MySingletonClass
{
    private static $_instance = null;
	
    public static function getInstance() 
    {
        if (self::$_instance === null)
            self::$_instance = new self();
 
        return self::$_instance;
    }
 
    final private function __construct() { }
    final private function __clone() { }
	
	private $_val;
	
	public function getValue()
	{
		return $this->_val;
	}
	
	public function setValue($val)
	{
		$this->_val = $val;
	}
}
 
$instance = MySingletonClass::getInstance();
$instance->setValue(1337);
print $instance->getValue()."<br />"; //1337

$instance2 = MySingletonClass::getInstance(); //same instance 
print $instance2->getValue()."<br />"; //1337

Strategy

Sehr elegante Lösung, wenn es für einen Anwendungsfall mehrere Strategien geben kann. Die Payment-Klasse muss es garnicht interessieren, was es für Bezahlverfahren gibt. Sie kann sich darauf verlassen, dass sie eine Instanz einer Strategie übergeben bekommt, die genau diesen Task erfüllt. Sinnvoll, um Klassen hoch erweiterbar zu halten und keine Lust auf endlose if / else Konstrukte hat.

interface IStrategy 
{
    public function execute();
}
 
class PayCreditCard implements IStrategy 
{
    public function execute() 
	{
        echo "Paying via Credit Card<br />";
    }
}
 
class PayCash implements IStrategy 
{
    public function execute() 
	{
        echo "Paying in cash<br />";
    }
}

//----------------------------------------------
 
class Payment 
{
    private $_strategy;
 
    public function __construct(IStrategy $strategy) 
	{
        $this->_strategy = $strategy;
    }
 
    public function execute() 
	{
        $this->_strategy->execute();
    }
}

//----------------------------------------------

$payment1 = new Payment(new PayCash());
$payment1->execute(); //Paying in cash

$payment2 = new Payment(new PayCreditCard());
$payment2->execute(); //Paying via Credit Card

Abschließend sei noch bemerkt, dass es hier und da ein paar Feinheiten bei den Patterns je nach Autor gibt. Der Grundgedanke bleibt aber der gleiche.

Weitere Posts:

Dieser Beitrag wurde unter php, Software Engineering veröffentlicht. Setze ein Lesezeichen auf den Permalink.

3 Antworten auf 5 PHP Patterns im Schnelldurchlauf: Factory, Iterator, Observer, Singleton, Strategy

  1. Hermann sagt:

    Moin.

    Sehr interessant, insbesondere der Iterator. Auch wenn jetzt ein Kollege fragt: „Hä? Pattern?“ kann ich auf diesen Eintrag verweisen.

  2. chris sagt:

    gute Kurzfassung! Danke!

  3. kriss sagt:

    Hallo!

    Ich glaube, du hast einen Fehler in deiner Implementierung des Iterators.
    Würde man eine Instanz mit einem Array-Eintrag haben:
    – Blick in current liefert Wert des ersten Eintrags, Index 0 im internen Array
    – Ruf an next erhöht Zähler von 0 auf 1
    – Blick in current erzeugt nun Fehler, weil im internen Array kein Index 1 existiert

    Ja, das tritt nicht auf, wenn man den Iterator mit Schleifen (foreach, while) verwendet.
    Aber die Methoden des Iterator-Interfaces sind öffentlich und somit auch von außen benutzbar. Ich denke, du solltest hier noch eine Prüfung/Fallunterscheidung einbauen und ggfs. NULL erwidern oder eine Exception werfen.

    Grüße,
    kriss

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert