Obserwujący i obserwowalny

Oto problem: projektujesz program, który będzie renderował dane opisujące trójwymiarową scenę w dwóch wymiarach. Program musi być modułowy i musi umożliwiać wiele jednoczesnych widoków tej samej sceny. Każdy widok musi umożliwiać przedstawienie sceny z innego punktu obserwacyjnego w różnych warunkach oświetleniowych. Co ważniejsze, jeśli jakakolwiek część podstawowej sceny ulegnie zmianie, widoki muszą się zaktualizować.

Żaden z tych wymagań nie stanowi nie do pokonania wyzwania programistycznego. Gdyby jednak kod, który obsługuje każde z wymagań, musiałby zostać napisany od nowa , oznaczałoby to znaczny nakład pracy. Na szczęście obsługę tych zadań zapewnia już biblioteka klas Javy w postaci interfejsu Observeri klasy - Observableinspirowane częściowo wymaganiami architektury MVC.

Architektura modelu / widoku / kontrolera (MVC)

Architektura Model / Widok / Kontroler została wprowadzona jako część Smalltalk, popularnego języka programowania obiektowego wymyślonego przez Alana Kaya. MVC został zaprojektowany w celu zmniejszenia wysiłku programistycznego wymaganego do tworzenia systemów wykorzystujących wiele zsynchronizowanych prezentacji tych samych danych. Jego główną cechą jest to, że model, kontrolery i widoki są traktowane jako oddzielne jednostki, a zmiany wprowadzone w modelu powinny być automatycznie odzwierciedlane w każdym z widoków.

Oprócz przykładu programu opisanego w pierwszym akapicie powyżej, architektura Model / Widok / Kontroler może być używana w projektach takich jak:

  • Pakiet wykresów zawierający równoczesne widoki wykresów słupkowych, liniowych i kołowych tych samych danych.
  • System CAD, w którym fragmenty projektu można oglądać przy różnych powiększeniach, w różnych oknach i w różnych skalach.

Rysunek 1 ilustruje architekturę MVC w jej najbardziej ogólnej formie. Jest jeden model. Wielu kontrolerów manipuluje modelem; wiele widoków wyświetla dane w modelu i zmienia się wraz ze zmianą stanu modelu.

Rysunek 1. Architektura modelu / widoku / kontrolera

Korzyści z MVC

Architektura Model / Widok / Kontroler ma kilka zalet:

  • Istnieje jasno określony rozdział między komponentami programu - problemy w każdej dziedzinie mogą być rozwiązywane niezależnie.
  • Istnieje dobrze zdefiniowany interfejs API - wszystko, co poprawnie używa interfejsu API, może zastąpić model, widok lub kontroler.
  • Powiązanie między modelem a widokiem jest dynamiczne - występuje w czasie wykonywania, a nie w czasie kompilacji.

Włączając architekturę MVC do projektu, fragmenty programu mogą być projektowane oddzielnie (i zaprojektowane tak, aby dobrze spełniały swoje zadanie), a następnie łączone razem w czasie wykonywania. Jeśli później okaże się, że komponent jest nieodpowiedni, można go wymienić bez wpływu na inne elementy. Porównaj ten scenariusz z monolitycznym podejściem typowym dla wielu szybkich programów Java. Często ramka zawiera cały stan, obsługuje wszystkie zdarzenia, wykonuje wszystkie obliczenia i wyświetla wynik. Tak więc we wszystkich takich systemach, z wyjątkiem najprostszych, wprowadzanie zmian po fakcie nie jest trywialne.

Definiowanie części

Model to obiekt reprezentujący dane w programie. Zarządza danymi i przeprowadza wszystkie transformacje tych danych. Model nie ma określonej wiedzy ani o swoich kontrolerach, ani o swoich widokach - nie zawiera żadnych wewnętrznych odniesień do żadnego z nich. Raczej sam system przejmuje odpowiedzialność za utrzymywanie powiązań między modelem a jego widokami i powiadamianie widoków o zmianie modelu.

Widok to obiekt zarządzający wizualnym wyświetlaniem danych reprezentowanych przez model. Tworzy wizualną reprezentację obiektu modelu i wyświetla dane użytkownikowi. Współdziała z modelem poprzez odniesienie do samego obiektu modelu.

Kontroler to obiekt, który zapewnia środki do interakcji użytkownika z danymi reprezentowanymi przez model. Zapewnia środki, za pomocą których wprowadzane są zmiany w informacjach w modelu lub w wyglądzie widoku. Współdziała z modelem poprzez odniesienie do samego obiektu modelu.

W tym miejscu może być pomocny konkretny przykład. Jako przykład rozważ system opisany we wstępie.

Rysunek 2. Trójwymiarowy system wizualizacji

Centralnym elementem systemu jest model trójwymiarowej sceny. Model jest matematycznym opisem wierzchołków i ścian tworzących scenę. Dane opisujące każdy wierzchołek lub ścianę można modyfikować (być może w wyniku wprowadzenia danych przez użytkownika, zniekształcenia sceny lub algorytmu morfingu). Nie ma jednak pojęcia punktu widzenia, sposobu wyświetlania (model szkieletowy lub bryła), perspektywy czy źródła światła. Model jest czystą reprezentacją elementów tworzących scenę.

Część programu, która przekształca dane w modelu w graficzną prezentację, to widok. Widok odzwierciedla rzeczywisty sposób wyświetlania sceny. Jest to graficzne przedstawienie sceny z określonego punktu widzenia w określonych warunkach oświetleniowych.

Kontroler wie, co można zrobić z modelem i implementuje interfejs użytkownika, który umożliwia zainicjowanie tej akcji. W tym przykładzie panel kontrolny wprowadzania danych może pozwolić użytkownikowi na dodawanie, modyfikowanie lub usuwanie wierzchołków i ścian.

Obserwujący i obserwowalny

Język Java obsługuje architekturę MVC z dwiema klasami:

  • Observer: Każdy obiekt, który chce być powiadamiany o zmianie stanu innego obiektu.
  • Observable: Dowolny przedmiot, którego stan może być interesujący i w którym inny przedmiot może zarejestrować zainteresowanie.

Te dwie klasy mogą służyć do implementacji znacznie więcej niż tylko architektury MVC. Są odpowiednie dla każdego systemu, w którym obiekty muszą być automatycznie powiadamiane o zmianach zachodzących w innych obiektach.

Zazwyczaj model jest podtypem, Observablea widok jest podtypem Observer. Te dwie klasy obsługują funkcję automatycznego powiadamiania MVC. Zapewniają mechanizm, dzięki któremu widoki mogą być automatycznie powiadamiane o zmianach w modelu. Odwołania do obiektu do modelu zarówno w kontrolerze, jak i widoku umożliwiają dostęp do danych w modelu.

Funkcje obserwatora i obserwowalne

Poniżej znajdują się listy kodów funkcji obserwatora i obserwowalnych:

Obserwator

  • public void update(Observable obs, Object obj)

    Wywoływane, gdy nastąpiła zmiana w stanie obserwowalnym.

Zauważalny

  • public void addObserver(Observer obs)

    Dodaje obserwatora do wewnętrznej listy obserwatorów.

  • public void deleteObserver(Observer obs)

    Usuwa obserwatora z wewnętrznej listy obserwatorów.

  • public void deleteObservers()

    Usuwa wszystkich obserwatorów z wewnętrznej listy obserwatorów.

  • public int countObservers()

    Zwraca liczbę obserwatorów na wewnętrznej liście obserwatorów.

  • 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 .