sobota, 17 października 2015

Jeśli trafiłeś gdzieś w środek serii, lepiej będzie jak odpalisz te posty w takiej kolejności:
  • Passive MVC vs MVP Passive View (wkrótce...) 
  • Active MVC vs MVP Passive View (wkrótce...) 
  • Passive MVC vs MVP Supervising Controller (wkrótce...) 
  • MVP Passive View i MVP Supervising Controllet (wkrótce...)
Podobnie jak w poprzednim poście o MVC, powstanie tutaj aplikacja, która umożliwia przesuwanie znaczka po ekranie, co oczywiście nie jest jakimś wyczynem. Celem tej aplikacji jest przećwiczenie teorii o MVC w praktyce, porównując warianty Passive i Active.

Na początku, wymienię różnice między Active i Passive MVC oraz w drugiej kolejności, analogicznie jak w poprzednim poście, zaimplementuję przykładową aplikację.

Passive MVC vs Active MVC

[Round 1]

  • Active MVC: Model posiada kolekcję Widoków, które są informowane przez Model o jego zmianie i konieczności odświeżania Widoku. Powiadamianie Widoków odbywa się za pomocą wzorca Obserwator, gdzie obiektem obserwowanym jest Model a obserwatorem jest Widok.
  • Passive MVC: Model nie posiada kolekcji Widoków i nie powiadamia ich o swojej zmianie. Role obiektu obserwowanego przejmuje na siebie Kontroler. Model jest kompletnie nieświadomy istnienia Widoku i Kontrolera. Model nie posiada żadnych zależności.

[Round 2]

  • Active MVC: Model zależy od Widoku w postaci kolekcji obserwatorów, których informuje o swojej zmianie.
  • Passive MVC: Kontroler oraz Widok zależą od Modelu ale Model nie zależy od niczego.

[Round 3]

  • Active MVC: Model, może (może ale nie musi) sam inicjować zmianę swojego stanu bez konieczności odpalania jego metod przez Kontroler.
  • Passive MVC: Model nie może samoczynnie zmienić swojego stanu. Każda zmiana stanu jest aktywowana przez wywołanie odpowiednich metod przez Kontroler.

[Round 4]

  • Active MVC & Passive MVC: Kontroler interpretuje akcje wykonane przez użytkownika na Widoku i wywołuje odpowiednią metodę na Modelu.

[Round 5]

  • Active MVC: Model sam decyduje o tym kiedy poinformować o swojej zmianie Widok.
  • Passive MVC: Model nie decyduje o konieczności powiadomienia Widoku o zmianie swojego stanu. To Kontroler podejmuje tą decyzję.

[Round 6]

  • Active MVC: Za każdym razem gdy Model zmieni swój stan, z automatu wywołuje metodę Update na Widoku, który zawsze się odświeża.
  • Passive MVC: Kontroler posiada kontrolę nad powiadamianiem Widoku o zmianie Modelu przez co może zdecydować o wydajności i optymalizacji. Nie wszystkie zmiany Modelu muszą odwieżać Widok.

[Round 7]

  • Active MVC: Kontroler jest tak prosty jak to jest możliwe a Widok jest tak głupi jak to tylko możliwe.
  • Passive MVC: Kontroler tak samo jak w Active odpala metody na Modelu ale jest nieco mądrzejszy, ponieważ decyduje o tym kiedy odświeżyć Widok.

[Round 8]

  • Active MVC: Wzorzec Obserwator występuje między Modelem a Widokiem gdzie Model jest obserwowany a Widok jest obserwatorem.
  • Passive MVC: Wzorzec Obserwator może występować między Kontrolerem a Widokiem gdzie Kontroler jest obserwowany a Widok jest obserwatorem.

[Round 9]

  • Active MVC: Po raz pierwszy aplikacja oparta o wzorzec MVC powstała w 1979 roku w języku SmallTalk, gdy WEB jeszcze nie istniał. MVC był projektowany wyłącznie dla aplikacji okienkowych. Dzisiaj już się go nie stosuje często w jego czystej postaci.
  • Passive MVC: Odmiana pasywna wzorca MVC jest adaptacją klasycznego wzorca MVC do dzisiejszych realiów, gdzie rządzi niepodzielnie WEB. Najbliżej tej odmianie do Modelu 2 (JSP) w Javie oraz ASP.NET MVC w .NET.
KOD APLIKACJI JEST NA GITHUB... TeoVincent/MVC-by-example

Model

  • Modelem jest obiekt będący punktem, posiadającym współrzędne X i Y. W przeciwieństwie do wersji Active, Model Passive nie posiada kolekcji obserwatorów oraz metody wywołującej powiadomienia tych obserwatorów.
  • Model jest czystym obiektem zawierającym tylko i wyłącznie logikę, bez jakichkolwiek zależności od Kontrolera oraz Widoku.
  • Jedynym ukłonem w stronę Kontrolera jest tutaj wystawienie publicznych getterów zwracających aktualną wartość współrzędnych X i Y.
public interface IModel
{
    int X { get; }
    int Y { get; }

    void IncreaseX();
    void DecreaseX();
    void IncreaseY();
    void DecreaseY();
}

public class Model : IModel
{
    public int X { get; private set; }
    public int Y { get; private set; }

    public void IncreaseX()
    {
        X++;
    }

    public void DecreaseX()
    {
        X--;
    }

    public void IncreaseY()
    {
        Y++;
    }

    public void DecreaseY()
    {
        Y--;
    }
}

Kontroler

  • Kontroler w trybie Passive posiada te same odpowiedzialności jakie posiadał w trybie Active oraz dodatkowo odpowiada za to, za co odpowiadał Model, czyli za powiadamianie Widoku o zmianie stanu Modelu.
  • Kontroler zapewnia Widokowi inteligencję.
  • Zawiera instancję Modelu, do którego deleguje wywołania otrzymane od Widoku.
  • Obsługuje wywołania otrzymane od Widoku.
  • Pobiera dane wejściowe użytkownika i określa ich znaczenie dla Modelu. Odpowiada za interpretację danych wejściowych i wykonanie odpowiednich operacji na Modelu.
  • Żąda od Modelu zmiany stanu.
  • Określa, jakie operacje powinny być wykonane na Modelu.
  • Oddziela logikę sterowania od Widoku separując od siebie Widok i Model.
  • Między Widokiem a Kontrolerem jest wzorzec Strategia. Kontroler jest zachowaniem Widoku.
  • Zmieniając Kontroler zmienimy zachowanie Widoku, bez konieczności zmiany Widoku.
  • Widok jest obiektem konfigurowalnym za pomocą odpowiedniej strategii, którą zapewnia Kontroler.
  • Najpierw Widok przekazuje Kontrolerowi informacje o czynnościach użytkownika, następnie Kontroler inicjuje operacje Modelu.
  • Kontroler może wykonywać pewną pracę związaną z określaniem, jakie metody Modelu mają zostać wywołane, ale nigdy nie należy to do funkcji określanych jako, "logika aplikacji". Logika aplikacji to kod, który zarządza i operuje danymi. Jest on w całości zawarty w Modelu.
  • Kontroler zawiera instancję Modelu oraz Widoku dzięki czemu wywołuje odpowiednie metody na Modelu oraz powiadamia Widok o zmianie stanu Modelu.
public interface IController
{
 void MoveRight();
 void MoveLeft();
 void MoveUp();
 void MoveDown();
}

public class Controller : IController
{
 private readonly IModel model;
 private readonly IObserver view;

 public Controller(IModel model, IObserver view)
 {
  this.model = model;
  this.view = view;
 }

 public void MoveRight()
 {
  model.IncreaseX();
  Notify();
 }

 public void MoveLeft()
 {
  model.DecreaseX();
  Notify();
 }

 public void MoveUp()
 {
  model.DecreaseY();
  Notify();
 }

 public void MoveDown()
 {
  model.IncreaseY();
  Notify();
 }

 protected override void Notify()
 {
  view.Update(model.X, model.Y);
 }
}

Widok

  • Nie ma różnic między Passive i Active w kontekście Widoku
  • Skoro Widok jest obserwatorem, to implementuje interfejs IObserver, w którym zdefiniowana jest metoda Update.
  • Widok otrzymuje powiadomienia o zmianie Modelu od Kontrolera za pomocą metody Update.
public interface IObserver
{
    void Update(int x, int y);
}
  • Metoda Update Widoku bazując na argumentach przekazanych od Kontrolera, odświeża swój Widok.
  • Poniżej przedstawiam przykładową implementację Widoku, który jest konsolą. W wyniku wywołania metody Update, Widok maluje się na nowo umieszczając znak '#' na miejscu przesuniętym w prawo o wartość 'x' oraz w dół o wartość 'y'. Za każdym razem gdy Model zmieni swój stan, czyli gdy zmienne x i y zmienią się w modelu, Kontroler wywoła metodę Update przekazując te wartości do Widoku, który namaluje się ponownie umieszczając znak '#' w nowym miejscu.
public class ConsoleView : IObserver
{
    private readonly int width;
    private readonly int height;
 
    public ConsoleView(int width, int height)
    {
        this.width = width;
        this.height = height;
    }
 
    public void Update(int x, int y)
    {
        Render(x, y);
    }
 
    private void Render(int x, int y)
    {
        Console.Clear();
 
        for (int i = 0; i < height; ++i)
        {
            for (int j = 0; j < width; ++j)
            {
                if (y == i && x == j)
                    Console.Write("#");
                else
                    Console.Write(" ");
            }
            Console.WriteLine();
        }
    }
}
  • Widać tutaj, że możemy implementować wiele różnych rodzajów Widoków bez konieczności zmiany implementacji Kontrolera oraz Modelu. Wystarczy tylko zaimplementować interface IObserver.
  • Aby użytkownik mógł za pomocą Widoku sterować aplikacją, Widok zawiera w sobie instancję Kontrolera.
  • Widok zawiera publiczną metodę SetController, za pomocą której, przypisuje sobie ten Kontroler.
  • Kontroler będzie służył Widokowi do delegowania akcji, jakie wykonał na Widoku użytkownik.
  • Metoda SetController definiowana jest w interfejsie, który każdy Widok implementuje.
public interface IView
{
    void SetController(IController controller);
}
 
public class ConsoleView : IObserver, IView
{
    private IController controller;
 
    // ... pozostała cześć klasy pozostaje niezmieniona
         
    public void SetController(IController controller)
    {
            this.controller = controller;
    }
}
  • Między Widokiem a Kontrolerem jest wzorzec Strategia. Kontroler jest strategią Widoku.
  • W obiekcie Widoku znajdują się metody umożliwiające interakcję użytkownika z Widokiem.
  • Widok nie wie jak ma zostać obsłużona dana akcja podjęta przez użytkownika.
  • Widok ma dwa zadania:
    • pierwsze - bycie interfejsem wyświetlającym informacje, o których został poinformowany przez Kontroler za pośrednictwem metody Update
    • drugie - umożliwienie interakcji z użytkownikiem, przekazując wywołania do Kontrolera, który podejmie decyzję co zrobić z danym żądaniem
  • Zadaniem Widoku nie jest obsługa interakcji użytkownika.
  • Widok oddelegowuje żądania użytkownika do Kontrolera.
  • Na potrzeby przykładu zaimplementuję możliwość zmieniania wartości x i y za pomocą strzałek na klawiaturze. Zdarzenia wciskania strzałek Widok oddeleguje do Kontrolera wywołując na tym Kontrolerze odpowiednie metody.
  • Poniżej jeszcze raz, tym razem już kompletna implementacja klasy ConsoleView. W porównaniu do poprzednich implementacji pojawiła się nowa metoda Navigate, która czeka na przyciśnięcia strzałek klawiatury.
public class ConsoleView : IObserver, IView
{
    private IController controller;
 
    private readonly int width;
    private readonly int height;
 
    public ConsoleView(int width, int height)
    {
        this.width = width;
        this.height = height;
    }
 
    public void Update(int x, int y)
    {
        Render(x, y);
    }
 
    private void Render(int x, int y)
    {
        Console.Clear();
 
        for (int i = 0; i < height; ++i)
        {
            for (int j = 0; j < width; ++j)
            {
                if (y == i && x == j)
                    Console.Write("#");
                else
                    Console.Write(" ");
            }
            Console.WriteLine();
        }
    }
 
    public void SetController(IController controller)
    {
        this.controller = controller;
    }
 
    public void Navigate()
    {
        while (true)
        {
            var key = Console.ReadKey();
 
            switch (key.Key)
            {
                case ConsoleKey.UpArrow:
                    controller.MoveUp();
                    break;
                case ConsoleKey.DownArrow:
                    controller.MoveDown();
                    break;
                case ConsoleKey.RightArrow:
                    controller.MoveRight();
                    break;
                case ConsoleKey.LeftArrow:
                    controller.MoveLeft();
                    break;
                case ConsoleKey.Escape:
                    return;
            }
        }
    }
}

Nowy wydajniejszy Widok

Dotychczas zaimplementowany Widok jest mało wydajny. Każde ponowne narysowanie znaku '#' pociąga za sobą drukowanie wszystkich wierszy konsoli na nowo.
Napisałem nową wydajniejszą wersję Widoku. Dzięki wzorcu MVC logika aplikacji od wyświetlania interfejsu jest sprytnie oddzielona, tak, że napisanie wydajniejszej wersji Widoku nie pociąga za sobą żadnych zmian w logice aplikacji. Nie ma innych zmian w całej aplikacji, jest tylko nowa wersja Widoku.
W nowym Widoku nie rysuję wszystkich linii na nowo. Efekt poruszania się znaku '#' rozwiązałem za pomocą metody SetCursorPosition klasy Console. Dodatkowo pokusiłem się o namalowanie tła. Implementacja wygląda tak:
public class ConsoleView : IObserver, IView
{
    private IController controller;

    private readonly int width;
    private readonly int height;

    private int currentX;
    private int currentY;

    public ConsoleView(int width, int height)
    {
        this.width = width;
        this.height = height;
        RenderBackground();
    }

    public void SetController(IController controller)
    {
        this.controller = controller;
    }

    public void Update(int x, int y)
    {
        Render(x, y);
    }

    public void Navigate()
    {
        while (true)
        {
            var key = Console.ReadKey();

            switch (key.Key)
            {
                case ConsoleKey.UpArrow:
                    controller.MoveUp();
                    break;
                case ConsoleKey.DownArrow:
                    controller.MoveDown();
                    break;
                case ConsoleKey.RightArrow:
                    controller.MoveRight();
                    break;
                case ConsoleKey.LeftArrow:
                    controller.MoveLeft();
                    break;
                case ConsoleKey.Escape:
                    return;
            }
        }
    }

    private void Render(int x, int y)
    {
        ClearLast(currentX, currentY);
        PrintNew(x, y);
    }

    private void ClearLast(int x, int y)
    {
        Console.SetCursorPosition(x, y);
        Console.Write(" ");
    }

    private void PrintNew(int x, int y)
    {
        if (x >= 0 && y >= 0)
        {
            Console.SetCursorPosition(x, y);
            Console.Write("#");

            currentX = x;
            currentY = y;
        }
    }

    private void RenderBackground()
    {
        Console.BackgroundColor = ConsoleColor.Blue;

        for (int i = 0; i < height; ++i)
        {
            for (int j = 0; j < width; ++j)
                Console.Write(" ");

            Console.WriteLine();
        }

        Console.SetCursorPosition(0, 0);
    }
}

WinForms Widok

W celach ćwiczebno-zaczepnych zaimplementowałem, tak dla sportu, nową całkiem inną wersję Widoku, opartą o inną technologię budowania interfejsu, nie zmieniając przy tym ani jednej linijki kodu w logice aplikacji.
public partial class WinFormView : Form, IObserver, IView
{
    private IController controller;

    private readonly int width;
    private readonly int height;
    private readonly int scale;

    private readonly Graphics graphics;

    public WinFormView(int width, int height, int scale)
    {
        this.width = width*scale;
        this.height = height*scale;
        this.scale = scale;

        InitializeComponent();
        ClientSize = new Size(this.width, this.height);
        graphics = CreateGraphics();
    }

    public void Update(int x, int y)
    {
        Render(x, y);
    }

    public void SetController(IController controller)
    {
        this.controller = controller;
    }

    private void Render(int x, int y)
    {
        ClearLast();
        PrintNew(x, y);
    }

    private void ClearLast()
    {
        graphics.Clear(Color.Azure);
    }

    private void PrintNew(int x, int y)
    {
        var point = new Point(x*scale, y*scale);
        var size = new Size(scale, scale);
        var rectangle = new Rectangle(point, size);
        graphics.FillEllipse(Brushes.Black, rectangle);
    }

    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        if (keyData == Keys.Up)
        {
            controller.MoveUp();
            return true;
        }

        if (keyData == Keys.Down)
        {
            controller.MoveDown();
            return true;
        }

        if (keyData == Keys.Left)
        {
            controller.MoveLeft();
            return true;
        }

        if (keyData == Keys.Right)
        {
            controller.MoveRight();
            return true;
        }

        return base.ProcessCmdKey(ref msg, keyData);
    }
}

Kontroler posiada kolekcję obserwatorów

Na sam koniec zaimplementuję wzorzec obserwator w Kontrolerze aby móc równocześnie wyświetlić więcej niż jeden Widok. Cała logika pozostaje bez zmian. Sterowanie aplikacji będzie możliwe za pomocą dwóch różnych Widoków bez konieczności najmniejszych zmian w logice aplikacji czyli w Modelu.
Kontroler jest obserwowany czyli musi posiadać swoich obserwatorów, więc obsługę subskrybowania obserwatorów można napisać raz w klasie abstrakcyjnej Observable.

public abstract class Observable
{
 protected readonly List observers;

 protected Observable()
 {
  observers = new List();
 }

 public void Subscribe(IObserver observer)
 {
  observers.Add(observer);
 }

 protected abstract void Notify();
}
Metoda Notify implementowana jest bezpośrednio w Kontrolerze, w którym ma ona dostęp do aktualnego stanu Modelu.
public class Controller : Observable, IController
{
 private readonly IModel model;

 public Controller(IModel model)
 {
  this.model = model;
 }

 public void MoveRight()
 {
  model.IncreaseX();
  Notify();
 }

 public void MoveLeft()
 {
  model.DecreaseX();
  Notify();
 }

 public void MoveUp()
 {
  model.DecreaseY();
  Notify();
 }

 public void MoveDown()
 {
  model.IncreaseY();
  Notify();
 }

 protected override void Notify()
 {
  foreach(var view in  observers)
   view.Update(model.X, model.Y);
 }
}

Zmieniam logikę

bez zmiany Widoku i Kontrolera.
Nowa logika, wnosi do modelu cztery ograniczenia:
  • Wartości x i y nie mogą być mniejsze niż zero.
  • Wartości x i y nie mogą być większe niż zdefiniowane ograniczenie.
  • Gdy x lub y posiada wartość zero i zostanie wywołane żądanie zmniejszenia to nowa wartość ma wynosić największą możliwą.
  • Gdy x lub y osiągnie największą możliwą wartość i zostanie wywołane żądanie zwiększenia wówczas wartość ta ma się wyzerować.
public class Model : IModel
{
 private readonly int minX;
 private readonly int maxX;

 private readonly int minY;
 private readonly int maxY;

 public int X { get; private set; }
 public int Y { get; private set; }

 public Model(int minX, int maxX, int minY, int maxY)
 {
  this.minX = minX;
  this.maxX = maxX;
  this.minY = minY;
  this.maxY = maxY;
 }

 public void IncreaseX()
 {
  if (X == maxX - 1)
   X = 0;
  else
   X++;
 }

 public void DecreaseX()
 {
  if (X == minX)
   X = maxX - 1;
  else
   X--;
 }

 public void IncreaseY()
 {
  if (Y == maxY - 1)
   Y = 0;
  else
   Y++;
 }

 public void DecreaseY()
 {
  if (Y == minY)
   Y = maxY - 1;
  else
   Y--;
 }
}
Wszystko działa jak się można było spodziewać. Wciskając klawisze nawigacyjne klawiatury, przesuwamy kursorem w wybranym kierunku, a jeśli osiągniemy granicę przestrzeni, wówczas przeskakujemy na przeciwległą stronę powierzchni, i ani jedna linijka Widoku i Kontrolera nie została przy tym zmieniona.

Zbudowanie zależności

Na sam już koniec kod budujący zależności i startujący aplikację.
class Program
{
 static void Main(string[] args)
 {
  int width = 50;
  int height = 50;
  int scale = 10;

  var model = new Model(0, width, 0, height);
  var controller = new Controller(model);
  var consoleView = new ConsoleView(width, height);
  var winFormsView = new WinFormView(width, height, scale);

  controller.Subscribe(consoleView);
  controller.Subscribe(winFormsView);

  consoleView.SetController(controller);
  winFormsView.SetController(controller);

  Task.Factory.StartNew(() => consoleView.Navigate());
  Application.Run(winFormsView);
 }
}
Powstała aplikacja, która umożliwia przesuwanie znaczka po ekranie, co nie jest jakimś wyczynem. Celem tej aplikacji jest przećwiczenie teorii o MVC, na kodzie nie w ASP.NET MVC.

KOD APLIKACJI JEST NA GITHUB... TeoVincent/MVC-by-example

Update #1

Aby podkreślić istotę różnicy między Active i Passive MVC postanowiłem zmienić implementację Modelu w wersji Active oraz Kontrolera w wersji Passive. Po opublikowaniu tego artykułu, między czasie pogooglowałęm jeszcze więcej na temat Active i Passive MVC. Zauważyłem, że nie często ale powtarza się jedno błędne zrozumienie różnicy między tymi dwoma odmianami. To czy MVC jest Active nie jest determinowane tym, że występuje wzorzec Obserwator pomiędzy Modelem a Widokiem. Wyznacznikiem odmiany Passive nie jest występowanie pojedynczej referencji do Widoku w Kontrolerze. Te dwa warunki mogą zajść ale nie są konieczne do tego aby odróżnić te dwie odmiany.
Najważniejszą równicą jest fakt, że w Active MVC model może sam zmieniać swój stan i przez co on sam informuje Widok o swojej zmianie. Informowanie to może być zaimplementowane przez wzorzec Obserwator ale równie dobrze może to odbywać się po przez pojedynczą instancję obiektu Obserwatora będącego Widokiem. W odmianie Active MVC, Model sam zmienia swój stan i sam wykonuje powiadomienia Widoku.
W przypadku Passive MVC, zmianą modelu zarządza Kontroler. Model posiada logikę biznesową ale sam nie może zmienić swojego stanu. W wyniku wywołań metod Modelu po przez Kontroler, Model zmienia swój stan. Skoro to Kontroler decyduje o tym kiedy Model zmieni swój stan, to również kontroler powiadamia Widok o zmianie Modelu. Tutaj jest podobna historia jak w przypadku Active MVC. Kontroler może posiadać kolekcję Obserwatorów będących Widokami lub może posiadać pojedynczą instancję Obserwatora będącego Widokiem.
To czy dany wzorzec jest Active czy Passive zależy od tego kto jest powodem zmian stanu Modelu a nie to czy mamy zaimplementowany wzorzec Obserwator czy nie.
public class Model : IModel, IObservable
{
    private readonly int minX;
    private readonly int maxX;

    private readonly int minY;
    private readonly int maxY;
        
    private int x;
    private int y;

    private readonly List<IObserver> observers;
    private readonly Task overAndOverAgain;
    private Action action;
    private const int INTERVAL = 50;
    private readonly object lockObj;

    public Model(int minX, int maxX, int minY, int maxY)
    {
        this.minX = minX;
        this.maxX = maxX;
        this.minY = minY;
        this.maxY = maxY;

        observers = new List<IObserver>();

        lockObj = new object();
        action = () => { };
        overAndOverAgain = Task.Factory.StartNew(OverAndOverAgain);
    }

    public void Subscribe(IObserver observer)
    {
        observers.Add(observer);
    }

    public void IncreaseX()
    {
        lock (lockObj)
            action = IncX;
    }

    public void DecreaseX()
    {
        lock(lockObj)
            action = DecX;
    }

    public void IncreaseY()
    {
        lock (lockObj)
            action = IncY;
    }

    public void DecreaseY()
    {
        lock (lockObj)
            action = DecY;
    }

    private void IncX()
    {
        if (x == maxX-1)
            x = 0;
        else
            x++;

        Notify();
    }

    private void DecX()
    {
        if (x == minX)
            x = maxX-1;
        else
            x--;

        Notify();
    }

    private void IncY()
    {
        if (y == maxY-1)
            y = 0;
        else
            y++;

        Notify();
    }

    private void DecY()
    {
        if (y == minY)
            y = maxY-1;
        else
            y--;

        Notify();
    }

    private void Notify()
    {
        foreach (var observer in observers)
            observer.Update(x, y);
    }

    private void OverAndOverAgain()
    {
        while (true)
        {
            lock (lockObj)
            {
                action();
                overAndOverAgain.Wait(INTERVAL);
            }
        }
    }
}
Poniżej, nowa wersja Kontrolera, w którym analogicznie jak w przypadku Modelu zaimplementowany jest task zmieniający stan modelu w stałym interwale czasowym.
public class Controller : IController, IObservable
{
    private readonly IModel model;
    private readonly Task overAndOverAgain;
    private Action action;
    private readonly List<IObserver> observers;
    private const int INTERVAL = 50;

    public Controller(IModel model)
    {
        this.model = model;

        observers = new List<IObserver>();

        action = () => { };
        overAndOverAgain = Task.Factory.StartNew(OverAndOverAgain);
    }

    public void Subscribe(IObserver observer)
    {
        observers.Add(observer);
    }

    private void Notify()
    {
        foreach (var view in observers)
            view.Update(model.X, model.Y);
    }

    public void MoveRight()
    {
        lock (action)
            action = model.IncreaseX;
    }

    public void MoveLeft()
    {
        lock (action)
            action = model.DecreaseX;
    }

    public void MoveUp()
    {
        lock (action)
            action = model.DecreaseY;
    }

    public void MoveDown()
    {
        lock (action)
            action = model.IncreaseY;
    }

    private void OverAndOverAgain()
    {
        while (true)
        {
            lock (action)
            {
                action();
                Notify();
                overAndOverAgain.Wait(INTERVAL);
            }
        }
    }

0 komentarze :

Prześlij komentarz