3.7. Obserwator (Observer)

3.7.1. Przeznaczenie

Pozwala na implementację zachowania rozgłaszania i konsumowania informacji o zmianach w danym obiekcie. Wzorzec Obserwator składa się z Obiektu Obserwowanego (ang. subject, observable) i Obserwatora (ang. listener, observer). Za każdym razem, kiedy Obiekt Obserwowany zmienia swój stan, wszyscy obserwujący go Obserwatorzy są informowani o zmianie. Takie działa pozwala na zmniejszenie powiązań pomiędzy obiektami. Zamiast ścisłego wiązania używane jest luźne wiązanie.

3.7.2. Przykłady

  • System kolejkowania wiadomości jest obserwowany, aby wyświetlić postęp w realizacji danego zadania w GUI.

3.7.3. Uwaga

W PHP są dostępne dwa interfejsy, dostępne w bibliotece SPL: SplSubject i SplObserver, które pozwalają zaimplementować wzorzec Obserwatora.

3.7.4. Diagram UML

Alt Observer UML Diagram

3.7.5. Kod

Ten kod znajdziesz również na GitHub.

User.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php

namespace DesignPatterns\Behavioral\Observer;

/**
 * User implements the observed object (called Subject), it maintains a list of observers and sends notifications to
 * them in case changes are made on the User object
 */
class User implements \SplSubject
{
    /**
     * @var string
     */
    private $email;

    /**
     * @var \SplObjectStorage
     */
    private $observers;

    public function __construct()
    {
        $this->observers = new \SplObjectStorage();
    }

    public function attach(\SplObserver $observer)
    {
        $this->observers->attach($observer);
    }

    public function detach(\SplObserver $observer)
    {
        $this->observers->detach($observer);
    }

    public function changeEmail(string $email)
    {
        $this->email = $email;
        $this->notify();
    }

    public function notify()
    {
        /** @var \SplObserver $observer */
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }
}

UserObserver.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

namespace DesignPatterns\Behavioral\Observer;

class UserObserver implements \SplObserver
{
    /**
     * @var User[]
     */
    private $changedUsers = [];

    /**
     * It is called by the Subject, usually by SplSubject::notify()
     *
     * @param \SplSubject $subject
     */
    public function update(\SplSubject $subject)
    {
        $this->changedUsers[] = clone $subject;
    }

    /**
     * @return User[]
     */
    public function getChangedUsers(): array
    {
        return $this->changedUsers;
    }
}

3.7.6. Testy

Tests/ObserverTest.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace DesignPatterns\Behavioral\Observer\Tests;

use DesignPatterns\Behavioral\Observer\User;
use DesignPatterns\Behavioral\Observer\UserObserver;
use PHPUnit\Framework\TestCase;

class ObserverTest extends TestCase
{
    public function testChangeInUserLeadsToUserObserverBeingNotified()
    {
        $observer = new UserObserver();

        $user = new User();
        $user->attach($observer);

        $user->changeEmail('foo@bar.com');
        $this->assertCount(1, $observer->getChangedUsers());
    }
}