07

Návrhový vzor Pozorovatel
(Observer)

Na tomto místě zmíníme jedno téma, které v podstatě s aplikacemi pro Android nesouvisí. Pro pokračování v příkladu z minulé kapitoly totiž potřebujeme předat informace o případných změnách záznamů či jejich přidání nebo odebrání komponentě ListView. Pro tyto situace se dobře hodí návrhový vzor Pozorovatel/Posluchač (Observer/Listener). Nebudeme zde problematiku návrhových vzorů rozebírat podrobně, ale zmíníme alespoň základní fakta. Omlouváme se pokročilejším čtenářům za zjednodušení, kterých se tím v některých aspektech dopustíme. Věříme, že pro naši cílovou skupinu čtenářů jsou tato zjednodušení vhodná.


Co jsou návrhové vzory?

Programátoři s delší praxí brzy zjistí, že některé situace se v projektech opakují. V našem případě je to situace, kdy jedna třída potřebuje být informována o změnách jiné třídy. Ať už se jedná o okno, které má reagovat na stisk tlačítka, o tabulku, která se má překreslit v důsledku načtení nových dat, nebo o ListView, kde potřebujeme aktualizovat data po úpravě některého záznamu.


Pro podobné situace a společné šablony řešení těchto situací se používá označení návrhové vzory (design patterns). Existuje tedy například návrhový vzor Pozorovatel/Posluchač (Observer/Listener), který popisuje obecné řešení situace, kdy jedna třída potřebuje být informována, kdykoli nastane zvolená událost v jiné třídě. Existují ale také další návrhové vzory pro jiné, často opakované situace.


Návrhový vzor Pozorovatel v Javě

Protože se návrhové vzory vyskytují často, nabízí moderní programovací jazyky v rámci svých knihoven standardizované implementace těchto návrhových vzorů. Pro návrhový vzor Pozorovatel nabízí Java třídu Observable a rozhraní Observer, které nám zjednoduší použití návrhového vzoru


Třída Observable

Třída Observable implementuje chování objektu, ve kterém se dějí změny a který chce o těchto změnách informovat pozorovatele, kteří o to požádají. Nabízí metody:

  • addObserver(Observer o)
    Přidá do seznamu pozorovatelů nový objekt. Kdykoli nastane změna, budou všichni takto zařazení pozorovatelé informování o této změně.

  • setChanged()
    Tuto metodu voláme v okamžiku, kdy nastane událost, o které mají být pozorovatelé informování. Metodu lze volat opakovaně.

  • notifyObservers()
    Tato metoda zkontroluje, zda došlo ke změně (zda byla volána metoda setChanged()). Pokud ano, rozešle všem pozorovatelům informaci o tom, že nastaly změny.

Třída nabízí i další metody, ale ty nebudeme v našem projektu potřebovat.


Rozhraní Observer

Rozhraní Observer implementují ty třídy, které chtějí být informovány o změnách. Implementace rozhraní vyžaduje přítomnost jediné metody:

  • update(Observable zdroj, Object o)
    Tuto metodu zavolá potomek třídy Observable v okamžiku, kdy bude chtít pozorovatele informovat o nastalé změně.

Pokračování příkladu – Můj pestrý život

Využijeme návrhového vzoru Pozorovatel k předávání informací o změnách v záznamech. Třída Zaznam bude Observable a bude sdělovat informace o změně svého stavu třídě Data. Třída data bude také Observable a předá informace o přidání, změně či rušení položek v seznamu záznamů hlavní aktivitě a tedy i komponentě ListView.


  1. Třídy Zaznam a Data nastavíme jako potoky třídy Observable:

  2. public class Zaznam extends Observable {
    
    public class Data extends Observable implements Observer{
     
    

  3. Ve třídě Data implementujeme rozhraní Observer a přidáme metodu update():

  4. public class Data extends Observable implements Observer{
         
        @Override
        public void update(Observable observable, Object o) {
            this.setChanged();
            this.notifyObservers();
        }
    

  5. Také ve hlavní aktivitě je třeba implementovat rozhraní Observer a překrýt metodu update(). Z metody update() zavoláme metodu aktualizuj(), kterou jsme si nachystali už v předchozí kapitole:

  6. public class MainActivity extends AppCompatActivity implements Observer {
        ...
        @Override
        public void update(Observable observable, Object o) {
            this.aktualizuj();
    }
    

  7. Při jakékoli změně dat, která se má oznámit pozorovatelům, doplníme volání metod setChanged() a notifyObservers(). Při každém přidávání záznamu ve třídě Data navíc musíme přidat novému záznamu jako pozorovatele aktuální instanci třídy Data:

  8. public class Zaznam extends Observable {
        
        public void invertujDulezite() {
            this.dulezite = ! this.dulezite;
            this.setChanged();
            this.notifyObservers();
        }
    
    public class Data extends Observable implements Observer{
        ArrayList<Zaznam> data = new ArrayList<Zaznam>();
        protected void nactiData() {
            ...
            
            z = new Zaznam("Terra Mystica", "Naučil jsem se novou deskovou hru.", false);
            z.addObserver(this);
            this.data.add(z);
            this.setChanged();
            this.notifyObservers();
        }
        ...
        @Override
        public void update(Observable observable, Object o) {
            this.setChanged();
            this.notifyObservers();
        }
    

Shrnutí

  1. Pokud chce třída předávat ostatním informace o změně dat, může zdědit od třídy Observable metody:

    1. addObserver(Observer o)… přidá do seznamu pozorovatelů další třídu pozorovatel,
    2. setChanged()… volá se v okamžiku, kdy se změní atributy třídy,
    3. a notifyObservers()… volá se v okamžiku, kdy chceme informovat pozorovatele.

  2. Pozorovatel musí implementovat rozhraní Observable a jeho metodu update(Observable zdroj, Object zprava).

  3. Obdobný mechanizmus posluchačů (Listener) používá i knihovna Swing pro tvorbu grafického rozhraní aplikací a další knihovny Javy.