Observer (návrhový vzor)
Observer (pozorovatel, vydavatel) je návrhový vzor.
Terminologie
- Vydavatel (pozorovaný), v anglickém originále "subject".
- Předplatitel (posluchač), v anglickém originále "observer".
Mezi předplatiteli a vydavateli zavádí vztah N:1 (jeden vydavatel má mnoho předplatitelů). V případě, že na straně vydavatele dojde ke změně vztahu či výskytu nějaké události, upozorní na to automaticky všechny své předplatitele. Vydavatel zná své předplatitele a může jich mít neomezené množství. Vydavatel má také veřejné rozhraní, skrze které se předplatitelé mohou přihlašovat nebo odhlašovat. Předplatitel má veřejné rozhraní, které má být použito k jeho upozornění na změnu na straně vydavatele. Toto veřejné rozhraní předá vydavateli při přihlašování. Alternativně může předplatitel vydavateli předat sám sebe jako parametr.
Při rozdělování systému na soubor spolupracujících tříd je třeba zachovat konzistenci mezi souvisejícími objekty. Řešení konzistence prostřednictvím těsného svázání (v angl. originále tight coupling) objektů není žádoucí, protože snižuje znovupoužitelnost objektů.
Použití
Návrhový vzor je vhodný pro následující případy:
- Abstrakce má dva parametry, které jsou na sobě navzájem závislé. Zapouzdřením parametrů je dosaženo větší obměnitelnosti a znovupoužitelnosti.
- Změna jednoho objektu vyžaduje změnu dalších objektů a není známo, kolik objektů je třeba změnit.
- Objekt má být schopen upozornit další objekty, aniž by o těchto objektech věděl detaily.
Příklad
Příkladem použití může být tabulkový editor (např. MS Excel nebo OpenOffice Spreadsheet), který odděluje aplikační data od prezentační vrstvy. Zdrojová data mohou být uživateli prezentována několika způsoby současně, například jako tabulka i jako graf. V případě, že dojde ke změně zdrojových dat, je třeba, aby všechny prezentující objekty, které daná zdrojová data používají, byly na tuto změnu upozorněny.
Strategie
Při upozorňování předplatitelů na změny na straně vydavatele je možné implementovat dvě různé strategie, podle toho, na čí straně je iniciativa. Nicméně v obou případech jde o netriviální a opakující se komunikační zátěž.
Tažná strategie
U tažné strategie vydavatel informuje předplatitele o změně svého stavu. Předplatitel si následně vyžádá od vydavatele parametry, které ho zajímají. Nevýhodou této strategie je, že předplatitel musí mít funkci, která bude vydavatele žádat o parametry, a to periodicky (polling). Strategii je vhodné použít v případech, kdy náklady vynaložené na následnou komunikaci jsou menší než náklady, které by byly vynaložené pro rozeslání všech parametrů všem předplatitelům.
Tlačná strategie
U tlačné strategie vydavatel zároveň s informací o změně svého stavu předá předplatitelům všechny své parametry: broadcast. Předplatitel si vybere pouze ty informace, které ho zajímají. (pokud vůbec nějaké) Tím je cílená vzájemná komunikace omezena na minimum. Tuto strategii je vhodné použít v případech, kdy předplatitele zajímají všechny, nebo většina parametrů, které rozesílá vydavatel. Nebo v případech, kdy by objem následné komunikace (předplatitel si vyžádá parametry, vydavatel je zasílá) přesáhl celkový objem komunikace, kdy vydavatel posílá rovnou všechny své parametry sám od sebe, bez určování dílčí položky.
Výhody
Abstraktní spojení mezi vydavatelem a předplatitelem. Každý vydavatel zná seznam svých předplatitelů, z nichž každý implementuje rozhraní abstraktní třídy Observer. Díky tomu je spojení mezi vydavatelem a předplatitelem volné (vydavatele nezajímá, jaké třídy je předplatitel).
Podpora všesměrového vysílání: broadcast. Na rozdíl od běžné komunikace, kdy objekty přesně určí adresáta své zprávy, návrhový vzor Observer podporuje všesměrovou komunikaci. Vydavatel neadresuje konkrétně každého svého předplatitele, ale automaticky posílá zprávu všem předplatitelům, kteří jsou k němu přihlášeni.
Nevýhody
Nečekané aktualizace. Vzhledem k tomu, že vydavatelé o sobě navzájem nevědí, může nastat situace, kdy změna stavu u jednoho objektu vyvolá vlnu aktualizací, která se kaskádovitě šíří (vydavatel může být zároveň předplatitelem jiného vydavatele). Navíc pokud nejsou správně definována kritéria závislostí, lze jen těžko vystopovat podvržené aktualizace. Samotný aktualizační protokol neposkytuje žádné info o zdroji, který změny vyvolal, což vystopování zdroje aktualizace ještě více ztěžuje.
Implementace
V některých případech má smysl, aby byl předplatitel přihlášen k více vydavatelům zároveň. V takovém případě je třeba rozšířit rozhraní pro aktualizace (a s ním související aktualizační protokol) tak, aby byl znám i zdroj aktualizací. Tím je zajištěna konzistence se správnými vydavateli. (integrita dat)
Pokud je třeba mazat vydavatele, je třeba mazání ošetřit tak, aby se předplatitelé neodkazovali na již neexistující objekty vydavatelů. Například tak, že při mazání vydavatel oznámí svým předplatitelům, že bude smazán. Předplatitelé tak mohou aktualizovat své odkazy.
Je třeba zajistit aby stav vydavatele byl, ve chvíli kdy upozorňuje své předplatitele na změnu, neměnný. Pokud by se jeho stav měnil i poté, co upozornil své předplatitele, mohlo by dojít k situaci kdy různí předplatitelé dostanou rozdílné informace o stavu vydavatele (v závislosti na pořadí a času, kdy si vyžádali jeho parametry).
V některých případech je vhodné rozšířit přihlašovací rozhraní i o předmět zájmu. Předplatitel při této akci specifikuje, které parametry ho zajímají. Vydavatel pak upozorňuje pouze ty předplatitele, které zajímá právě změněný parametr.
Zapouzdření komplexní sémantiky aktualizací. Pokud je na vydavateli závislé velké množství předplatitelů a zároveň vydavatelé vyžadují oznámení o změně v jiném formátu, je vhodné použít SprávceZměn (v angl. originále ChangeManager). Tento přijme aktualizaci od vydavatele a předtím, než jí zprostředkuje předplatitelům, jí upraví tak, aby jí předplatitelé rozuměli. SprávceZměn má tři úkoly. Shromažďuje požadavky předplatitelů na formát aktualizace a poskytuje rozhraní, které toto shromažďování umožňuje. Definuje aktualizační strategie (tažná vs. tlačná). Posledním úkolem je, že na žádost vydavatele aktualizuje všechny závislé předplatitele.
Kombinace tříd vydavatele a předplatitele. Programovací jazyky, které nepodporují vícenásobnou dědičnost, obecně definují pouze jednu třídu, která kombinuje rozhraní obou objektů (předplatitele i vydavatele). Příkladem takového programovacího jazyku je Smalltalk.
Příklady použití
První a zřejmě nejznámější implementace návrhového vzoru Observer je v programovacím jazyce Smalltalk jako Model – View – Controller (MVC). Model je v tomto případě sdílenou informací, Controller (řadič) je vydavatelem, který šíří aktualizaci modelu, a View (Pohled) je předplatitelem.
Příklad v programovacím jazyku Java
Soubor myapp.java obsahuje metodu main(), která je určena pro spuštění kódu.[1]
/* File Name : EventSource.java */
package obs;
import java.util.Observable; //Observable is here
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class EventSource extends Observable implements Runnable
{
public void run()
{
try
{
final InputStreamReader isr = new InputStreamReader( System.in );
final BufferedReader br = new BufferedReader( isr );
while( true )
{
String response = br.readLine();
setChanged();
notifyObservers( response );
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
/* File Name: ResponseHandler.java */
package obs;
import java.util.Observable;
import java.util.Observer; /* this is Event Handler */
public class ResponseHandler implements Observer
{
private String resp;
public void update (Observable obj, Object arg)
{
if (arg instanceof String)
{
resp = (String) arg;
System.out.println("\nReceived Response: "+ resp );
}
}
}
/* Filename : myapp.java */
/* This is the main program */
package obs;
public class MyApp
{
public static void main(String args[])
{
System.out.println("Enter Text >");
// create an event source - reads from stdin
final EventSource evSrc = new EventSource();
// create an observer
final ResponseHandler respHandler = new ResponseHandler();
// subscribe the observer to the event source
evSrc.addObserver( respHandler );
// starts the event thread
Thread thread = new Thread(evSrc);
thread.start();
}
}
Související návrhové vzory
Reference
- Wikipedia: Observer (pattern). 29 November 2009 [cit. 7.12.2009]. EN. Dostupný z WWW: <http://en.wikipedia.org/wiki/Observer_pattern>.
- IBM: Design pattern projects - example. 27 August 2001 [cit. 7.12.2009]. EN. Dostupný z WWW: <http://www.research.ibm.com/designpatterns/example.htm Archivováno 12. 2. 2009 na Wayback Machine>.