Promatrač i Promatranost

Evo problema: Dizajnirate program koji će prikazivati ​​podatke koji opisuju trodimenzionalnu scenu u dvije dimenzije. Program mora biti modularan i mora dopuštati više istovremenih prikaza iste scene. Svaki pogled mora biti u mogućnosti prikazati scenu s druge točke gledišta, pod različitim uvjetima osvjetljenja. Još važnije, ako se bilo koji dio temeljne scene promijeni, pogledi se moraju sami ažurirati.

Nijedan od ovih zahtjeva ne predstavlja nepremostiv programski izazov. Ako bi se kôd koji obrađuje svaki zahtjev morao pisati novo , to bi dodalo značajan rad ukupnom naporu. Srećom, podršku za ove zadatke već pruža knjižnica Java klasa u obliku sučelja Observeri klase - Observableoboje nadahnute, dijelom, zahtjevima MVC arhitekture.

Arhitektura modela / prikaza / kontrolera (MVC)

Arhitektura Model / View / Controller predstavljena je kao dio Smalltalka, popularnog objektno orijentiranog programskog jezika koji je izumio Alan Kay. MVC je dizajniran da smanji napor programiranja potreban za izgradnju sustava koji koriste višestruke, sinkronizirane prezentacije istih podataka. Njegove su središnje karakteristike da se model, kontroleri i pogledi tretiraju kao zasebni entiteti i da bi se promjene napravljene u modelu trebale automatski odraziti u svakom od prikaza.

Pored primjera programa opisanog u prethodnom odlomku, arhitektura Model / Pogled / Upravljač može se koristiti za projekte kao što su sljedeći:

  • Paket grafikona koji sadrži simultane trakasti grafikon, linijski grafikon i tortni grafikon istih podataka.
  • CAD sustav, u kojem se dijelovi dizajna mogu pregledavati s različitim uvećanjima, u različitim prozorima i u različitim mjerilima.

Slika 1 ilustrira MVC arhitekturu u njenom najopćenitijem obliku. Postoji jedan model. Više modela kontrolira model; više pogleda prikazuje podatke u modelu i mijenja se kako se stanje modela mijenja.

Slika 1. Arhitektura modela / pogleda / kontrolera

Prednosti MVC-a

Arhitektura Model / View / Controller ima nekoliko prednosti:

  • Jasno je definirano razdvajanje komponenata programa - problemi u svakoj domeni mogu se riješiti neovisno.
  • Postoji dobro definiran API - sve što API koristi pravilno može zamijeniti model, pogled ili kontroler.
  • Vezanje između modela i pogleda je dinamično - događa se u vrijeme izvođenja, a ne u vrijeme kompajliranja.

Uključivanjem MVC arhitekture u dizajn, dijelovi programa mogu se dizajnirati odvojeno (i dizajnirani da dobro rade svoj posao), a zatim povezati u vrijeme izvođenja. Ako se kasnije komponenta smatra neprikladnom, može se zamijeniti bez utjecaja na ostale dijelove. Suprotstavite tom scenariju monolitni pristup tipičan za mnoge brze i prljave Java programe. Često okvir sadrži sve stanje, obrađuje sve događaje, vrši sve izračune i prikazuje rezultat. Stoga, u svim, osim u najjednostavnijim sustavima, provođenje promjena nakon činjenice nije trivijalno.

Definiranje dijelova

Model je objekt koji predstavlja podatke u programu. Upravlja podacima i provodi sve transformacije tih podataka. Model nema specifično znanje ni o kontrolorima ni o stavovima - ne sadrži interne reference ni na jedan. Umjesto toga, sam sustav preuzima odgovornost za održavanje veza između modela i njegovih pogleda i obavještavanje o pogledima kada se model promijeni.

Pogled je objekt koji upravlja vizualnim prikazom podataka predstavljenih modelom. Izrađuje vizualni prikaz objekta modela i prikazuje podatke korisniku. On komunicira s modelom putem reference na sam objekt modela.

Upravljač je objekt koji pruža sredstva za interakciju korisnika s podacima predstavljenim modelom. Pruža način na koji se vrše promjene, bilo u informacijama u modelu ili u izgledu pogleda. On komunicira s modelom putem reference na sam objekt modela.

U ovom trenutku konkretan primjer može biti od pomoći. Razmotrimo kao primjer sustav opisan u uvodu.

Slika 2. Trodimenzionalni sustav vizualizacije

Središnji dio sustava je model trodimenzionalne scene. Model je matematički opis vrhova i lica koja čine scenu. Podaci koji opisuju svaki vrh ili lice mogu se modificirati (možda kao rezultat korisničkog unosa ili algoritma izobličenja scene ili morfinga). Međutim, ne postoji pojam gledišta, načina prikaza (žičani ili puni), perspektive ili izvora svjetlosti. Model je čisti prikaz elemenata koji čine scenu.

Dio programa koji transformira podatke u modelu u grafički prikaz je prikaz. Pogled utjelovljuje stvarni prikaz scene. To je grafički prikaz scene s određene točke gledišta, pod određenim uvjetima osvjetljenja.

Upravljač zna što se može učiniti s modelom i implementira korisničko sučelje koje omogućuje pokretanje te radnje. U ovom primjeru upravljačka ploča za unos podataka može dopustiti korisniku dodavanje, izmjenu ili brisanje vrhova i lica.

Promatrač i Promatranost

Java jezik podržava MVC arhitekturu s dvije klase:

  • Observer: Bilo koji objekt koji želi biti obaviješten kada se stanje drugog objekta promijeni.
  • Observable: Bilo koji objekt čije stanje može biti od interesa i u kojem drugi objekt može registrirati interes.

Ove dvije klase mogu se koristiti za implementaciju mnogo više od same MVC arhitekture. Prikladni su za bilo koji sustav u kojem objekte treba automatski obavijestiti o promjenama koje se događaju u drugim objektima.

Tipično je model podvrsta, Observablea pogled podtip Observer. Ove dvije klase obrađuju MVC-ovu automatsku funkciju obavijesti. Oni pružaju mehanizam pomoću kojeg se pogledi mogu automatski obavijestiti o promjenama u modelu. Referencije objekata na model i u upravljaču i u pogledu omogućuju pristup podacima u modelu.

Promatračke i uočljive funkcije

Slijede popisi kodova za promatrača i uočljive funkcije:

Posmatrač

  • public void update(Observable obs, Object obj)

    Pozvan kada je došlo do promjene stanja promatranja.

Uočljiv

  • public void addObserver(Observer obs)

    Dodaje promatrača na unutarnji popis promatrača.

  • public void deleteObserver(Observer obs)

    Briše promatrača s unutarnjeg popisa promatrača.

  • public void deleteObservers()

    Briše sve promatrače s unutarnjeg popisa promatrača.

  • public int countObservers()

    Vraća broj promatrača na internom popisu promatrača.

  • protected void setChanged()

    Sets the internal flag that indicates this observable has changed state.

  • protected void clearChanged()

    Clears the internal flag that indicates this observable has changed state.

  • public boolean hasChanged()

    Returns the boolean value true if this observable has changed state.

  • public void notifyObservers()

    Checks the internal flag to see if the observable has changed state and notifies all observers.

  • public void notifyObservers(Object obj)

    Checks the internal flag to see if the observable has changed state and notifies all observers. Passes the object specified in the parameter list to the notify() method of the observer.

Next we'll take a look at how to create a new Observable and Observer class, and how to tie the two together.

Extend an observable

A new class of observable objects is created by extending class Observable. Because class Observable already implements all of the methods necessary to provide the desired behavior, the derived class need only provide some mechanism for adjusting and accessing the internal state of the observable object.

In the ObservableValue listing below, the internal state of the model is captured by the integer n. This value is accessed (and, more importantly, modified) only through public accessors. If the value is changed, the observable object invokes its own setChanged() method to indicate that the state of the model has changed. It then invokes its own notifyObservers() method in order to update all of the registered observers.

Listing 1. ObservableValue

 import java.util.Observable; public class ObservableValue extends Observable { private int n = 0; public ObservableValue(int n) { this.n = n; } public void setValue(int n) { this.n = n; setChanged(); notifyObservers(); } public int getValue() { return n; } } 

Implement an observer

A new class of objects that observe the changes in state of another object is created by implementing the Observer interface. The Observer interface requires that an update() method be provided in the new class. The update() method is called whenever the observable changes state and announces this fact by calling its notifyObservers() method. The observer should then interrogate the observable object to determine its new state, and, in the case of the MVC architecture, adjust its view appropriately.

In the following TextObserver listing, the notify() method first checks to ensure that the observable that has announced an update is the observable that this observer is observing. If it is, it then reads the observable's state, and prints the new value.

Listing 2. TextObserver

 import java.util.Observer; import java.util.Observable; public class TextObserver implements Observer { private ObservableValue ov = null; public TextObserver(ObservableValue ov) { this.ov = ov; } public void update(Observable obs, Object obj) { if (obs == ov) { System.out.println(ov.getValue()); } } } 

Tie the two together

A program notifies an observable object that an observer wishes to be notified about changes in its state by calling the observable object's addObserver() method. The addObserver() method adds the observer to the internal list of observers that should be notified if the state of the observable changes.

The example below, showing class Main, demonstrates how to use the addObserver() method to add an instance of the TextObserver class (Listing 2) to the observable list maintained by the ObservableValue class (Listing 1).

Listing 3. addObserver()

 public class Main { public Main() { ObservableValue ov = new ObservableValue(0); TextObserver to = new TextObserver(ov); ov.addObserver(to); } public static void main(String [] args) { Main m = new Main(); } } 

How it all works together

The following sequence of events describes how the interaction between an observable and an observer typically occurs within a program.

  1. First the user manipulates a user interface component representing a controller. The controller makes a change to the model via a public accessor method -- which is setValue() in the example above.
  2. The public accessor method modifies the private data, adjusts the internal state of the model, and calls its setChanged() method to indicate that its state has changed. It then calls notifyObservers() to notify the observers that it has changed. The call to notifyObservers() could also be performed elsewhere, such as in an update loop running in another thread.
  3. The update() methods on each of the observers are called, indicating that a change in state has occurred. The observers access the model's data via the model's public accessor methods and update their respective views.

Observer/Observable in an MVC architecture

Now let's consider an example demonstrating how observables and observers typically work together in an MVC architecture. Like the model in the ObservableValue (Listing 1) the model in this example is very simple. Its internal state consists of a single integer value. The state is manipulated exclusively via accessor methods like those in ObservableValue. The code for the model is found here.

Initially, a simple text view/controller class was written. The class combines the features of both a view (it textually displays the value of the current state of the model) and a controller (it allows the user to enter a new value for the state of the model). The code is found here.

By designing the system using the MVC architecture (rather than embedding the code for the model, the view, and the text controller in one monolithic class), the system is easily redesigned to handle another view and another controller. In this case, a slider view/controller class was written. The position of the slider represents the value of the current state of the model and can be adjusted by the user to set a new value for the state of the model. The code is found here.

About the author

Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed object applications in C++, Todd moved to the Java programming language when Java became the obvious choice for that sort of thing.

This story, "Observer and Observable" was originally published by JavaWorld .