PHP - Factory Design Pattern

07/07/2016 15:23 luki.dc#1
Hallo zusammen,

Aktuell bin ich am PHP programmieren lernen und ich habe da eine Frage zum Factory Design Pattern.

Ich verstehe nach vielen stunden recherchieren das Factory Pattern immer noch nicht ganz und wäre froh wenn mich jemand aufklären könnte.

Bis an hin verstehe ich das so:
Ich habe eine Klasse, nennen wir diese loginHandler.
In dieser klasse mache ich mehrere Instanzen/Objekte auf. z.B:

Code:
public function __construct()
        {
            $this->databaseWrapper = new DatabaseWrapper;
            $this->passwordHasher = new HashPassword($this->enteredPassword);
            $this->getUserValues = new UserFromDatabase($this->enteredUsername);
        }
Anstatt dass ich nun jetzt z.B. in einer anderen Klasse auch wieder das HashPassword Objekt benötige, und es wieder einbinden muss kann ich doch eine Factory Class machen in welcher ich dann machen kann:

Code:
public function getHasher() {
    return new HashPassword($this->enteredPassword)
}
somit muss ich im Handler nur noch die Class Factory einbinden und kann dann getHasher machen ?


Verstehe ich das richtig oder liege ich hier falsch...



Die ganzen Beispiele mit den Autos usw. verstehe ich überhaupt nicht...





PS: noch eine andere Frage..
Mein Valueobject sollte keine Logic enthalten. Weiter unten werdet Ihr einen Teil von mienem ValueObject sehen. Kann ich das irgendwie verbessern?


Code:
public function __construct($username)
        {
            $this->selectUserFromDatabasequery = new SelectUserFromDatabaseQuery;
            $this->user = $this->selectUserFromDatabasequery->execute($username);            
        }
        
        public function getUsername()
        {
            return $this->user['username'];
        }
07/07/2016 16:33 MrSm!th#2
Ich würde mal behaupten, dass das Factory Pattern eins der beliebtesten und gleichzeitig eins der am meisten missverstandenen Patterns ist. Das liegt sicher auch an oftmals irreführenden Beispielen.

Grob gesagt hat das Factory Pattern den Sinn, die Erstellung der betroffenen Objekte zu kapseln und zu abstrahieren, damit sich der verwendende Code nicht damit beschäftigen muss.
Warum will man die Erstellung von Objekten abstrahieren? Nun, meistens will man das nicht und dann ist das Pattern auch völlig fehl am Platz. Wenn die Factory eigentlich nichts abstrahiert bzw. unnötigerweise abstrahiert und sich in ihrer Verwendung nicht von einem direkten Konstruktoraufruf unterscheidet, dann bietet sie keinen Mehrwert:

Code:
abstract class User {}
class NormalUser extends User {}
class Admin extends User {}

abstract class UserFactory
{
	public abstract function createUser();
}

class NormalUserFactory extends UserFactory
{
    public function createUser()
    {
        return new NormalUser();
    }
}

class AdminFactory extends UserFactory
{
    public function createUser()
    {
        return new Admin();
    }
}

class SomeClass
{
    public function doStuff($user_type)
    {
        if ($user_type === 'admin')
        {
            $factory = new AdminFactory();    
        }
        else
        {
            $factory = new NormalUserFactory();
        }
        $user = $factory->createUser();
    }
}
Diese Factory könnte man sich z.B. völlig sparen, weil du damit nichts abstrahierst und es in diesem Fall vielleicht auch gar nicht nötig wäre. Wenn man sich in SomeClass nicht auf die konkrete Implementierung von User festlegen will, kann man z.B. auch einfach den User als Parameter in die Methode bekommen und der Aufrufer entscheidet, ob er einen Admin oder einen normalen User übergibt. In vielen falsch verwendeten Factories würde es eigentlich reichen, das erstellte Objekt selbst in die jeweilige Methode hereinzureichen, weil es eigentlich nur darum geht, die Implementierung des Objekts zu abstrahieren, aber nicht die Erstellung.

Eine Factory brauchst du wirklich nur dann, wenn du jederzeit ein Objekt, das eine bestimmte Schnittstelle implementiert, erstellen können musst, ohne den konkreten Typ zu kennen.

Wie könnte so ein Beispiel aussehen? Na ja, stellen wir uns mal vor, PHP wäre eine Systemsprache (ich weiß, lol) und wir würden ein betriebssystemunabhängiges Window Management implementieren (ja, das wirkt etwas komplex, aber Factories sind nun mal in simplen Systemen i.d.R. einfach nur unnötig):

Code:
abstract class Window
{
	/* some generic window stuff */
	public abstract function setTitle($title);
	public abstract function show();
}

class LinuxWindow extends Window
{
	/* linux-specific implementation */
}

class WindowsWindow extends Window
{
	/* windows-specific implementation */
}

abstract class WindowFactory
{
	public abstract function createWindow();
}

class LinuxWindowFactory extends WindowFactory
{
	public function createWindow()
	{
		/* complex linux-specific code */
		return new LinuxWindow(/* linux-specific parameters */);
	}
}

class WindowsWindowFactory extends WindowFactory
{
	public function createWindow()
	{
		/* complex windows-specific code */
		return new WindowsWindow(/* windows-specific parameters */);
	}
}

class WindowManager
{
	private $wndfact;
	private $all_windows;
	
	public function __construct(WindowFactory $w)
	{
		$this->wndfact = $w;
	}
	
	/* window manager stuff */
	
	public function spawnWindow($window_title)
	{
		$wnd = $this->wndfact->createWindow();
		$wnd->setTitle($window_title);
		/* some more complex init stuff */
		$wnd->show();
		$this->all_windows[] = $wnd;
		
		return $wnd;
	}
}

$window_factory = get_platform() === 'windows' 
                          ? new WindowsWindowFactory()
                          : new LinuxWindowFactory();

$window_manager = new WindowManager($window_factory);

/* do stuff with $window_manager */
Das Ganze ist natürlich stark vereinfacht, aber ich hoffe, das Prinzip wird klar. Der WindowManager ist eine komplexe Klasse, die alle Fenster verwaltet (d.h. sie aktualisiert, Events auslöst etc.). Dabei muss er auch Fenster erstellen können, was in der Methode spawnWindow passiert (es könnte aber auch einfach im Rahmen anderer Funktionalitäten nötig sein, neue Fenster zu erstellen, z.B. in einer Methode wie showMessageBox).

Nun willst du den WindowManager aber nicht für jedes Betriebssystem neu implementieren, wenn er eigentlich immer das gleiche macht und die einzige systemspezifische Eigenschaft das Erzeugen des Window-Objekts ist (weil eben für unterschiedliche Systeme unterschiedliche Daten und Systemaufrufe innerhalb des Windows verwendet werden).

Die Lösung ist nun eine Factory: Du abstrahierst die Erstellung von Fenstern weg, sodass der WindowManager seine Funktionalität auf die abstrakten Klassen Window und WindowFactory stützt. Wie die funktionieren und was die zurückgeben, ist ihm egal. Er macht seinen Job auf Basis der Schnittstellen, die die abstrakten Klassen bereitstellen. Dem WindowManager übergibst du dann einfach eine Factory deiner Wahl, die die korrekten Window-Objekte für einen bestimmten Kontext (z.B. je nach Betriebssystem) für den WindowManager erstellt. So musst du für jedes System nur Window und die WindowFactory implementieren, aber der WindowManager funktioniert immer gleich.

Das ist der Mehrwert einer korrekt eingesetzten Factory: Wenn Objekte auf unterschiedliche Weisen erstellt werden können/müssen, aber du diese Unterscheidungslogik nicht in deiner Klasse XY haben willst, dann brauchst du eine Factory, die du an XY übergibst.

Deine zweite Frage verstehe ich nicht wirklich.
07/07/2016 18:38 luki.dc#3
Quote:
Originally Posted by MrSm!th View Post
Ich würde mal behaupten, dass das Factory Pattern eins der beliebtesten und gleichzeitig eins der am meisten missverstandenen Patterns ist. Das liegt sicher auch an oftmals irreführenden Beispielen.

Grob gesagt hat das Factory Pattern den Sinn, die Erstellung der betroffenen Objekte zu kapseln und zu abstrahieren, damit sich der verwendende Code nicht damit beschäftigen muss.
Warum will man die Erstellung von Objekten abstrahieren? Nun, meistens will man das nicht und dann ist das Pattern auch völlig fehl am Platz. Wenn die Factory eigentlich nichts abstrahiert bzw. unnötigerweise abstrahiert und sich in ihrer Verwendung nicht von einem direkten Konstruktoraufruf unterscheidet, dann bietet sie keinen Mehrwert:

Code:
abstract class User {}
class NormalUser extends User {}
class Admin extends User {}

abstract class UserFactory
{
	public abstract function createUser();
}

class NormalUserFactory extends UserFactory
{
    public function createUser()
    {
        return new NormalUser();
    }
}

class AdminFactory extends UserFactory
{
    public function createUser()
    {
        return new Admin();
    }
}

class SomeClass
{
    public function doStuff($user_type)
    {
        if ($user_type === 'admin')
        {
            $factory = new AdminFactory();    
        }
        else
        {
            $factory = new NormalUserFactory();
        }
        $user = $factory->createUser();
    }
}
Diese Factory könnte man sich z.B. völlig sparen, weil du damit nichts abstrahierst und es in diesem Fall vielleicht auch gar nicht nötig wäre. Wenn man sich in SomeClass nicht auf die konkrete Implementierung von User festlegen will, kann man z.B. auch einfach den User als Parameter in die Methode bekommen und der Aufrufer entscheidet, ob er einen Admin oder einen normalen User übergibt. In vielen falsch verwendeten Factories würde es eigentlich reichen, das erstellte Objekt selbst in die jeweilige Methode hereinzureichen, weil es eigentlich nur darum geht, die Implementierung des Objekts zu abstrahieren, aber nicht die Erstellung.

Eine Factory brauchst du wirklich nur dann, wenn du jederzeit ein Objekt, das eine bestimmte Schnittstelle implementiert, erstellen können musst, ohne den konkreten Typ zu kennen.

Wie könnte so ein Beispiel aussehen? Na ja, stellen wir uns mal vor, PHP wäre eine Systemsprache (ich weiß, lol) und wir würden ein betriebssystemunabhängiges Window Management implementieren (ja, das wirkt etwas komplex, aber Factories sind nun mal in simplen Systemen i.d.R. einfach nur unnötig):

Code:
abstract class Window
{
	/* some generic window stuff */
	public abstract function setTitle($title);
	public abstract function show();
}

class LinuxWindow extends Window
{
	/* linux-specific implementation */
}

class WindowsWindow extends Window
{
	/* windows-specific implementation */
}

abstract class WindowFactory
{
	public abstract function createWindow();
}

class LinuxWindowFactory extends WindowFactory
{
	public function createWindow()
	{
		/* complex linux-specific code */
		return new LinuxWindow(/* linux-specific parameters */);
	}
}

class WindowsWindowFactory extends WindowFactory
{
	public function createWindow()
	{
		/* complex windows-specific code */
		return new WindowsWindow(/* windows-specific parameters */);
	}
}

class WindowManager
{
	private $wndfact;
	private $all_windows;
	
	public function __construct(WindowFactory $w)
	{
		$this->wndfact = $w;
	}
	
	/* window manager stuff */
	
	public function spawnWindow($window_title)
	{
		$wnd = $this->wndfact->createWindow();
		$wnd->setTitle($window_title);
		/* some more complex init stuff */
		$wnd->show();
		$this->all_windows[] = $wnd;
		
		return $wnd;
	}
}

$window_factory = get_platform() === 'windows' 
                          ? new WindowsWindowFactory()
                          : new LinuxWindowFactory();

$window_manager = new WindowManager($window_factory);

/* do stuff with $window_manager */
Das Ganze ist natürlich stark vereinfacht, aber ich hoffe, das Prinzip wird klar. Der WindowManager ist eine komplexe Klasse, die alle Fenster verwaltet (d.h. sie aktualisiert, Events auslöst etc.). Dabei muss er auch Fenster erstellen können, was in der Methode spawnWindow passiert (es könnte aber auch einfach im Rahmen anderer Funktionalitäten nötig sein, neue Fenster zu erstellen, z.B. in einer Methode wie showMessageBox).

Nun willst du den WindowManager aber nicht für jedes Betriebssystem neu implementieren, wenn er eigentlich immer das gleiche macht und die einzige systemspezifische Eigenschaft das Erzeugen des Window-Objekts ist (weil eben für unterschiedliche Systeme unterschiedliche Daten und Systemaufrufe innerhalb des Windows verwendet werden).

Die Lösung ist nun eine Factory: Du abstrahierst die Erstellung von Fenstern weg, sodass der WindowManager seine Funktionalität auf die abstrakten Klassen Window und WindowFactory stützt. Wie die funktionieren und was die zurückgeben, ist ihm egal. Er macht seinen Job auf Basis der Schnittstellen, die die abstrakten Klassen bereitstellen. Dem WindowManager übergibst du dann einfach eine Factory deiner Wahl, die die korrekten Window-Objekte für einen bestimmten Kontext (z.B. je nach Betriebssystem) für den WindowManager erstellt. So musst du für jedes System nur Window und die WindowFactory implementieren, aber der WindowManager funktioniert immer gleich.

Das ist der Mehrwert einer korrekt eingesetzten Factory: Wenn Objekte auf unterschiedliche Weisen erstellt werden können/müssen, aber du diese Unterscheidungslogik nicht in deiner Klasse XY haben willst, dann brauchst du eine Factory, die du an XY übergibst.

Deine zweite Frage verstehe ich nicht wirklich.


Woow ! Super erklärt danke vielmals !
Nun merke ich dass meine Gedanken völlig falsch waren...
Also ist es so, dass wenn ich in mehreren Klassen im Konstruktor eine Instanz von meinem DatabaseWrapper mache ich dass nicht mit einer Factory machen kann sondern muss es jedes mal einzeln machen richtig ?
Oder kann man das irgendwie vereinfachen...

Code:
<?php
/**
 * Created by PhpStorm.
 * User: Lukas
 * Date: 02.06.2016
 * Time: 20:57
 */

namespace c2b\Handlers {
    
    use c2b\ValueObjects\HashPassword;
    use c2b\ValueObjects\UserFromDatabase;
    use c2b\Wrappers\DatabaseWrapper;

    class LoginHandler
    {
        /**
         *  [MENTION=881620]Var[/MENTION] DatabaseWrapper
         *
         */
        private $databaseWrapper;

        /**
         *  [MENTION=881620]Var[/MENTION] string
         *
         */
        private $enteredUsername;
        private $enteredPassword;

        /**
         *  [MENTION=881620]Var[/MENTION] UserFromDatabase
         *
         */
        private $getUserValues;

        /**
         *  [MENTION=881620]Var[/MENTION] HashPassword
         *
         */
        private $passwordHasher;

        public function __construct()
        {
            $this->databaseWrapper = new DatabaseWrapper;
            $this->enteredUsername = filter_input(INPUT_POST, 'username');
            $this->enteredPassword = filter_input(INPUT_POST, 'password');
            $this->passwordHasher = new HashPassword($this->enteredPassword);
            $this->getUserValues = new UserFromDatabase($this->enteredUsername);
        }

        private function createSessionName()
        {
            $_SESSION['name'] = $this->getUserValues->getUsername();
        }

        public function execute()
        {
            $passwordHashFromDB = $this->getUserValues->getPasswordHash();
            $usernameFromDB = $this->getUserValues->getUsername();

            if(password_verify($this->enteredPassword, $passwordHashFromDB) and($this->enteredUsername == $usernameFromDB)) {
                $this->createSessionName();
                header('Location: /Profile?id=' . $_SESSION['name']);
                return;
            }
                echo 'Invalid username or password';
        }
    }
}

Etwas anderes, Ich habe ein Query welches mir den gewünschten User in einem Array zurück gibt.
Mit einem Valueobject (Ich hoffe es ist ein begriff für dich) will ich mir die einzelnen Daten zurück geben.

So sieht es aus:

Code:
<?php
/**
 * Created by PhpStorm.
 * User: Lukas
 * Date: 04.06.2016
 * Time: 15:38
 */

namespace c2b\ValueObjects {


    use c2b\Queries\SelectUserFromDatabaseQuery;

    class UserFromDatabase
    {
        /**
         * [MENTION=881620]Var[/MENTION] SelectUserFromDatabaseQuery
         * 
         */
        private $selectUserFromDatabasequery;

        /**
         * [MENTION=881620]Var[/MENTION] string
         * 
         */
        private $user;
        
        public function __construct($username)
        {
            $this->selectUserFromDatabasequery = new SelectUserFromDatabaseQuery;
            $this->user = $this->selectUserFromDatabasequery->execute($username);            
        }
        
        public function getUsername()
        {
            return $this->user['username'];
        }

        public function getPasswordHash()
        {
            return $this->user['password'];
        }
        
        public function getFirstName()
        {
            return $this->user['firstname'];
        }

        public function getLastName()
        {
            return $this->user['lastname'];
        }

        public function getJoinDate()
        {
            return $this->user['joined'];
        }

        public function getGroup()
        {
            return $this->user['group'];
        }
    }
}
Nun siehst du aber dass im Konstruktor etwas gemacht wird... Ich möchte da aber keine Logik sondern nur dass ich die Variable bekomme... Wenn ¨überhaupt...


gibts da eine möglichkeit ?
07/07/2016 19:14 .Scy#4
zu deiner zweiten frage: wenn du die logik außerhalb des konstruktors haben willst, dann baust du dir eben einen wrapper, z.b. ne private selectUser($username) damit hast du die ganze logik ausgelagert, aber eben nur in einen wrapper, wäre ziemlich sinnlos aber ist möglich.

für sachen die sich häufig wiederholen kannst du natürlich auch einfach "wrapper" bauen die du von überall aufrufen kannst, wie das speziell in php geht weiß ich nicht.
07/07/2016 23:48 Devsome#5
#moved…
07/08/2016 16:15 Menan#6
in deinen ValueObjects solltest du keine Logik haben.

Wenn es um Datenbank sachen geht (sprich die Implementierung eines Data Access Objects), dann sollte dein DAO das VO generieren und zurückgeben (bzw eine List von VOs).

Schau dir hierzu mal folgendes an:
[Only registered and activated users can see links. Click Here To Register...]

Eventuell hilft es dir ja ;)