[WikiDyd] [TitleIndex] [WordIndex

(opracował: Robert Szmurło http://www.iem.pw.edu.pl/~szmurlor)

Interfejs zawiadamiający

(ang. Callback interface) celem niniejszego przykładu jest pokazanie jak obiekty, które są tworzone przez inne obiekty mogą zawiadamiać swoich ' twórców ' o pewnych zdarzeniach. Przykład związany jest z programowaniem zdarzeniowym.

W przedstawionym przykładzie zakładamy, że mamy pewien zbiór rzek (klasa River). Rzeki posiadają poziom wody, który charakteryzuje ich stan. Jeżeli poziom rzeki przekroczy wartość graniczną (atrybut riverLevelMax), to stacje pomiarowe powinny poinformować operatorów o sytuacji alarmowej.

W naszym konkretnym przykładzie, stacja pomiarowa jest realizowana przez klasę RiverEventSink. Zadaniem tej klasy jest nasłuchiwanie na wszystkie komunikaty, które wysyła rzeka. W momencie, gdy poziom alarmowy zostanie przekroczony, RiverEventSink wyświetli na ekranie komunikat. Dodatkowo, gdy poziom rzeki wzrośnie do wartości o 10% mniejszej od wartości maksymalnej, operator również powinien o tym być powiadomiony. Cały projekt będzie składać się z trzech klas i jednego interfejsu. Diagram dla naszego przykładu został przedstawiony na poniższym rysunku. Możemy z niego przeczytać: RiverEventSink implementuje interfejs IRiverStationEvents, River zawiera w sobie odwołania do obiektów typu IRiverStationEvents, Program jest klasą, która zawiera refencje do obiektów River oraz RiverEventSink.

attachment:callback_interface_class.png

W celu realizacji postawionego zadania, definiujemy specjalny interfejs, za pomocą którego rzeka będzie komunikować się ze stacjami pomiarowymi:

   1     public interface IRiverStationEvents
   2     {
   3         void AboutToFlood(string msg);
   4         void Flooded(string msg);
   5     }

Interfejs ten będzie zaimplementowany przez stację pomiarową, która dla odpowiednich metod będzie wypisywać komunikaty na ekranie. Interfejs ten będzie wykorzystywany przez obiekt River. Na poniższym rysunku przedstawiony został diagram interakcji między obiektami.

attachment:callback_interface_interactions.png

Kolejne kroki oznaczają odpowiednio dla numerów:

  1. Tworzymy nowy obiekt typu klasa River.
  2. Tworzymy nowy obiekt stacji pomiarowej typu RiverEventSink.

  3. Zwracamy się do obiektu River, aby dodał do swojej listy nową stacje pomiarową.
  4. Obiekt River dodaje do swojej listy nową stację pomiarową.
  5. Wykonywanie programu wraca do głównej funkcji Main.
  6. Ustawiamy atrybut Level na wartość np. 12, tak aby przekraczała wartość graniczną.
  7. Podczas uaktualniania swojego atrybutu, obiekt River stwierdza, że została przekroczona wartość graniczna i za pomocą interfejsu  IRiverStationEvents  informuje obiekt nasłuchujący czyli stację pomiarową, że wystąpiła powódź.

  8. Stacja pomiarowa wyświetla na konsoli operatora informację o powodzi.
  9. Kontrola wraca dla metody obsługującej uaktualnianie atrybutu Level.
  10. Kontrola wraca do programu Main.

Przyjrzyjmy, się jak klasa RiverEventSink czyli nasza stacja pomiarowa zaimplementuje zdefiniowany wcześniej interfejs  IRiverStationEvents  :

   1     // prosze zwrocic uwage ze implementujemy interfejs IRiverStationEvents
   2     public class RiverEventSink : IRiverStationEvents
   3     {
   4         // pomocnicza zmienna prywatna pozwalajaca identyfikowac
   5         // dana stacje pomiarowa.
   6         private string name;
   7 
   8         // dwa konstruktory
   9         public RiverEventSink() { }
  10         public RiverEventSink(string sinkName) {
  11             name = sinkName;
  12         }
  13 
  14         // ponizej implementujemt nasz interfejs
  15         public void AboutToFlood(string msg)
  16         {
  17             Console.WriteLine("{0} reporting: {1}", name, msg);
  18         }
  19         public void Flooded(string msg)
  20         { 
  21             Console.WriteLine("{0} reporting: {1}", name, msg);
  22         }
  23     }

Przeanalizujmy zawartość klasy definiującej rzekę. Na początek przyjrzyjmy się atrybutom. Pierwszy atrybut  name  jest oczywisty: jest to nazwa rzeki. Drugi atrybut  private ArrayList reporters = new ArrayList();  zawiera listę stacji pomiarowych, które rzeka będzie zawiadamiać w sytuacjach krytycznych. Trzeci i czwarty atrybut definiują odpowiednio aktualny i maksymalny poziom rzeki po którego przekroczeniu występuje powódź (ang. flood).

   1     public class River
   2     {
   3         private string name;
   4         private ArrayList reporters = new ArrayList();
   5         private int riverLevel = 0;
   6         private int riverLevelMax = 10;
   7 
   8         (...)
   9     }

Dodajmy do naszej klasy dwie metody, które będą rejestrowały w rzece stacje pomiarowe, które będzie miała ona informować o zaistniałych sytuacjach krytycznych. Pierwsza metoda  public void Report( IRiverStationEvents reporter )  dodaje nową stacje pomiarową do listy, druga metoda public  void UnReport( IRiverStationEvents reporter )  usuwą daną stację z listy.

   1     public class River
   2     {
   3         (...)
   4 
   5         public void Report( IRiverStationEvents reporter )
   6         {
   7             reporters.Add( reporter );
   8         }
   9 
  10         public void UnReport( IRiverStationEvents reporter )
  11         {
  12             reporters.Remove( reporter );
  13         }
  14 
  15         (...)

Proces zawiadamiania stacji pomiarowych umieścimy w części obsługującej atrybut  Level  odpowiedzialny za uaktualnianie i udostępnianie informacji o aktualnym poziomie rzeki. W metodzie  set  umieścimy logikę sprawdzającą czy nowy poziom, rzeki przekracza wartość dopuszczalną.

   1         public int Level {
   2             set {
   3                 // ustaw nowa wartosc poziomu rzeki
   4                 riverLevel = value;
   5 
   6                 // sprawdz czy nowy poziom przekracza wartosc graniczna
   7                 // powyzej ktorej nastepuje powodz.
   8                 if (riverLevel > riverLevelMax ) {
   9 
  10                     // poinformuj wszystkie stacje pomiarowe, ze wystapila powodz
  11                     foreach (IRiverStationEvents e in reporters)
  12                         e.Flooded("The river "+name+" has flooded... ");
  13 
  14                 } else {
  15 
  16                     // czy poziom rzeki jest powyzej poziomu alarmowego?
  17                     if (riverLevel > (riverLevelMax - (riverLevelMax / 10)) )
  18 
  19 
  20                         // poinformuj wszystkie stacje pomiarowe, ze wystapilo ryzyko powodzi
  21                         foreach (IRiverStationEvents e in reporters)
  22                             e.AboutToFlood("The river "+name+" is about to flood... ");
  23                 }
  24             }
  25             get { return riverLevel; }
  26         }

Po zebraniu kodu do jednej przestrzeni nazw  RiverLevelInterface  oraz uzupelnieniu jej o klasę testującą zawierającą funkcję  Main(...)  kod programu wygląda następująco:

   1 using System;
   2 using System.Collections;
   3 using System.Collections.Generic;
   4 using System.Text;
   5 
   6 /* Zbior stacji pomiarowych, mierzacych poziom wody.
   7  */
   8 namespace RiverLevelInterface
   9 {
  10 
  11     public interface IRiverStationEvents
  12     {
  13         void AboutToFlood(string msg);
  14         void Flooded(string msg);
  15     }
  16 
  17     public class RiverEventSink : IRiverStationEvents
  18     {
  19         private string name;
  20         public RiverEventSink() { }
  21         public RiverEventSink(string sinkName) {
  22             name = sinkName;
  23         }
  24         public void AboutToFlood(string msg)
  25         {
  26             Console.WriteLine("{0} reporting: {1}", name, msg);
  27         }
  28         public void Flooded(string msg)
  29         { 
  30             Console.WriteLine("{0} reporting: {1}", name, msg);
  31         }
  32     }
  33 
  34     public class River
  35     {
  36         private string name;
  37         private ArrayList reporters = new ArrayList();
  38         private int riverLevel = 0;
  39         private int riverLevelMax = 10;
  40 
  41         public River( string riverName )
  42         {
  43             name = riverName;
  44         }
  45 
  46         public override string ToString()
  47         {
  48             return name;
  49         }
  50 
  51         public void Report( IRiverStationEvents reporter )
  52         {
  53             reporters.Add( reporter );
  54         }
  55 
  56         public void UnReport( IRiverStationEvents reporter )
  57         {
  58             reporters.Remove( reporter );
  59         }
  60 
  61         public int Level {
  62             set {
  63                 riverLevel = value;
  64                 if (riverLevel > riverLevelMax ) {
  65                     foreach (IRiverStationEvents e in reporters)
  66                         e.Flooded("The river "+name+" has flooded... ");
  67                 } else {
  68                     if (riverLevel > (riverLevelMax - (riverLevelMax / 10)) )
  69                         foreach (IRiverStationEvents e in reporters)
  70                             e.AboutToFlood("The river "+name+" is about to flood... ");
  71                 }
  72             }
  73             get { return riverLevel; }
  74         }
  75     }
  76 
  77     class Program
  78     {
  79         static void Main(string[] args)
  80         {
  81             River river = new River("Wisla");
  82             RiverEventSink listener = new RiverEventSink("Plock");
  83 
  84             river.Report(listener);
  85 
  86             for (int i = 0; i < 15; i++)
  87             {
  88                 Console.WriteLine("Setting river level... {0}", i);
  89                 river.Level = i;
  90             }
  91 
  92             Console.ReadLine();
  93         }
  94     }
  95 }

Wynik na ekranie:

Setting river level... 0
Setting river level... 1
Setting river level... 2
Setting river level... 3
Setting river level... 4
Setting river level... 5
Setting river level... 6
Setting river level... 7
Setting river level... 8
Setting river level... 9
Setting river level... 10
Plock reporting: The river Wisla is about to flood...
Setting river level... 11
Plock reporting: The river Wisla has flooded...
Setting river level... 12
Plock reporting: The river Wisla has flooded...
Setting river level... 13
Plock reporting: The river Wisla has flooded...
Setting river level... 14
Plock reporting: The river Wisla has flooded...

2015-09-23 06:32