「HEAD-FIRST」のオブザーバーモード


これはデザインモードシリーズです.本書のすべてのケースは「Head-Firstデザインモード(中国語版)」から来ています.Githubアドレス、ようこそwatchstarオブザーバモード
オブジェクト間の1対の多依存性を定義し、1つのオブジェクトが状態を変更すると、そのすべての依存者が通知を受け取り、自動的に更新する.
オブザーバパターン形容図
デザインパズル
気象観測ステーションがあり、3種類の掲示板(異なる気象データを表示するためのもの)があり、気象ステーションが最新の測定データを取得した場合、3種類の掲示板がリアルタイムで更新することを望んでいる.
クラス図設計
このうちWeatherData気象庁の最新の測定データを取得するため(3つgetメソッド)、データが更新と、onChangedメソッドが呼び出される(なぜか、これは気象庁内部のロジックである).
コード実装
トピックインタフェース
interface Sublect
{
    public function registerObserver(Observer $observer);

    public function removeObserver();

    public function nitifyObservers();
}

テーマオブジェクトWeatherData
class WeatherData implements Sublect
{
    protected $observers = [];

    protected $pressure, $temperature, $humidity;

    public function registerObserver(Observer $observer)
    {
        if (array_search($observer, $this->observers) === false) {
            $this->observers[] = $observer;
        }
    }

    public function removeObserver()
    {
        if (($index = array_search($observer, $this->observers)) !== false) {
            unset($this->observers[$index]);
        }
    }

    public function nitifyObservers()
    {
        foreach ($this->observers as $observer) {
            $observer->update($this->getPressure(), $this->getTemperature(), $this->getHumidity());
        }
    }

    public function onChanged()
    {
        $this->nitifyObservers();
    }

    //      
    public function getPressure()
    {
        return $this->pressure;
    }

    //      
    public function getTemperature()
    {
        return $this->temperature;
    }

    //      
    public function getHumidity()
    {
        return $this->humidity;
    }

    //  
    public function youNeedChanged()
    {
        $this->pressure = mt_rand(1, 99);
        $this->temperature = mt_rand(1, 99);
        $this->humidity = mt_rand(1, 99);

        $this->onChanged();
    }
}

オブザーバインタフェース
interface Observer
{
    //  /  /  
    public function update($pressure, $temperature, $humidity);
}

パネルインタフェースの表示
interface DisplayElement
{
    public function display();
}

オブザーバオブジェクトセット
class CurrentConditionsDisplay implements Observer, DisplayElement
{
    protected $subject;

    protected $pressure, $temperature, $humidity;

    //         Subject             remove   registe
    public function __construct(Sublect $subject)
    {
        $this->subject = $subject;
        $this->subject->registerObserver($this);
    }

    public function update($pressure, $temperature, $humidity)
    {
        $this->pressure = $pressure;
        $this->temperature = $temperature;
        $this->humidity = $humidity;

        $this->display();
    }

    public function display()
    {
        echo "Current pressure: {$this->pressure}, Current temperature: {$this->temperature}";
    }
}

//         

テスト
$weatherData = new WeatherData();

$display = new CurrentConditionsDisplay($weatherData);//             
//$other = new OthersDisplay($weatherData);//             
//$other = new OtherDisplay($weatherData);//             

$weatherData->youNeedChanged();//                  
//Current pressure: 33, Current temperature: 46

別の形態の観察者モード
観察者は常にテーマオブジェクトのプッシュを受動的に受け入れることを知っていますが、一部のシーンでは、観察者が積極的にデータを取得することを望んでいます.何しろ観察者の数がこんなに多いから、テーマオブジェクトが各観察者に必要な状態を事前に知ることは不可能であり、わずかなデータしか必要としないのに、大量のデータを受け取ることを余儀なくされることもない.
設計上の問題を書き直しましょう.
クラス図はほぼ変わらないが、WeatherDataクラスに追加されたsetChangedメソッドが変更するObserverインタフェースupdate署名.
リビルドされたトピックインタフェース
interface Sublect
{
    public function registerObserver(Observer $observer);
    public function removeObserver();
    public function nitifyObservers($args = null);
}

interface Observer
{
    public function update(Sublect $subject, $object = null);
}

リビルドされたトピックオブジェクト
class WeatherData implements Sublect
{
    protected $observers = [];

    protected $pressure, $temperature, $humidity, $changed;

    public function nitifyObservers($args = null)
    {
        if ($this->changed) {
            foreach ($this->observers as $observer) {
                $observer->update($this, $args);
            }
            $this->changed = false;
        }
    }

    public function onChanged()
    {
        $this->setChanged();

        $this->nitifyObservers([
            'pressure' => $this->pressure,
            'temperature' => $this->temperature,
            'humidity' => $this->humidity,
        ]);
    }

    public function setChanged()//    
    {
        $this->changed = true;
    }

    //        
}

再構築された掲示板の対象
class CurrentConditionsDisplay implements Observer, DisplayElement
{
    protected $subject;

    protected $pressure, $temperature, $humidity;

    //         Subject             remove   registe
    public function __construct(Sublect $subject)
    {
        $this->subject = $subject;
        $this->subject->registerObserver($this);
    }

    public function update(Sublect $subject, $object = null)
    {
        if ($subject instanceof Sublect) {
            //                 
            $this->pressure = $subject->getPressure();
            $this->temperature = $subject->getTemperature();
            $this->humidity = $subject->getHumidity();

            //           
            $this->pressure = $object['pressure'];
            $this->temperature = $object['temperature'];
            $this->humidity = $object['humidity'];
        }


        $this->display();
    }

    public function display()
    {
        echo "Current pressure: {$this->pressure}, Current temperature: {$this->temperature}";
    }
}

なぜ1つ追加するのかsetChanged方法
setChangedは、観察者を更新する際に、より弾力性があり、観察者により適切に通知することができます.例えば、setCanged方法がなければ、気象ステーションの温度が10分の1変化したとき、すべての観察者に通知されます.このような頻繁な更新はしたくないでしょう.温度変化が一度になるように制御、setChangedを呼び出し、効率的な更新を行うことができる.