10
System powiadamiania: zdarzenia i wzorzec obserwatora
Cel zadania

Zrozumienie mechanizmu zdarzeń (events) w C# oraz implementacja wzorca projektowego Wydawca-Subskrybent (Publisher-Subscriber). Student nauczy się definiować własne klasy argumentów zdarzeń, stosować bezpieczne wywołanie eventów oraz zarządzać subskrypcjami w celu uniknięcia wycieków pamięci.

Scenariusz problemowy

Finał Twojego wielkiego projektu dla komisu "Auto-Premium" to inteligentny system powiadomień. Wyobraź sobie sytuację: kiedy cena konkretnego luksusowego auta ulega zmianie (np. z powodu nowej promocji lub negocjacji), inne działy firmy muszą dowiedzieć się o tym natychmiast. Biuro Sprzedaży musi zaktualizować ofertę na stronie, a Dział Marketingu wysłać newsletter do zainteresowanych klientów.

Zamiast ręcznie wywoływać metody w dziesięciu różnych klasach z poziomu obiektu samochodu (co stworzyłoby sztywny, trudny w utrzymaniu kod), wykorzystujesz zdarzenia (events). Twoja klasa "Samochod" staje się wydawcą, który po prostu "ogłasza światu" fakt zmiany ceny. Klasy "DzialSprzedazy" czy "Marketing" po prostu subskrybują to zdarzenie. Kiedy cena się zmieni, system automatycznie poinformuje wszystkich zainteresowanych. To kwintesencja luźnego powiązania (loose coupling) w architekturze oprogramowania – klasy wiedzą o sobie tylko tyle, ile jest niezbędne do współpracy. Dowiesz się również, dlaczego tak ważne jest odsubskrybowanie, gdy powiadomienia nie są już potrzebne, aby nie zaśmiecać pamięci niepotrzebnymi obiektami.

Opis wykonania
  • Stwórz klasę CenaZmienionaEventArgs dziedziczącą po EventArgs.
  • Dodaj do niej właściwości StaraCena oraz NowaCena.
  • W klasie Samochod zdefiniuj zdarzenie: public event EventHandler<CenaZmienionaEventArgs> OnCenaZmieniona;
  • W setterze właściwości Cena dodaj logikę, która sprawdza czy cena faktycznie się zmieniła i jeśli tak – wywołuje zdarzenie.
  • Użyj bezpiecznego wywołania zdarzenia: OnCenaZmieniona?.Invoke(this, args);
  • Stwórz klasę BiuroSprzedazy z metodą obsługującą zdarzenie (musi pasować do sygnatury delegata).
  • W Main utwórz auto i obiekt biura sprzedaży.
  • Zasubskrybuj zdarzenie przy użyciu operatora +=.
  • Zmień cenę auta i zaobserwuj reakcję biura sprzedaży w konsoli.
  • Odsubskrybuj zdarzenie ( operator -= ) i sprawdź, czy kolejna zmiana ceny wywoła jeszcze powiadomienie.
  • Zasubskrybuj zdarzenie dodatkowo przy użyciu wyrażenia lambda – to szybki sposób na dodanie logiki powiadomienia.
Kod źródłowy
Program.cs
using System;

namespace LaboratoriumOOP
{
    // KLASA ARGUMENTÓW: Przekazuje dane o zdarzeniu do subskrybentów.
    public class CenaZmienionaEventArgs : EventArgs
    {
        public double StaraCena { get; }
        public double NowaCena { get; }

        public CenaZmienionaEventArgs(double stara, double nowa)
        {
            StaraCena = stara;
            NowaCena = nowa;
        }
    }

    public class Samochod
    {
        private double _cena;
        public string Marka { get; set; }

        // ZDARZENIE (Event): Oparte na standardowym delegacie EventHandler.
        public event EventHandler<CenaZmienionaEventArgs> OnCenaZmieniona;

        public double Cena
        {
            get => _cena;
            set
            {
                if (_cena != value)
                {
                    double stara = _cena;
                    _cena = value;
                    // Wyzwalanie zdarzenia
                    OnCenaZmieniona?.Invoke(this, new CenaZmienionaEventArgs(stara, value));
                }
            }
        }
    }

    // SUBSKRYBENT: Klasa zainteresowana informacją o zdarzeniu.
    public class BiuroSprzedazy
    {
        public void ReagujNaZmianeCeny(object sender, CenaZmienionaEventArgs e)
        {
            Samochod s = (Samochod)sender;
            Console.WriteLine($"[BIURO] Cena pojazdu {s.Marka} uległa zmianie!");
            Console.WriteLine($" -> Stara: {e.StaraCena} PLN, Nowa: {e.NowaCena} PLN.");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("--- ZADANIE 10: Zdarzenia i subskrypcje ---\n");

            Samochod auto = new Samochod { Marka = "Tesla Model 3", Cena = 200000 };
            BiuroSprzedazy biuro = new BiuroSprzedazy();

            // 1. SUBSKRYPCJA (+=)
            auto.OnCenaZmieniona += biuro.ReagujNaZmianeCeny;

            // 2. SUBSKRYPCJA LAMBDĄ
            auto.OnCenaZmieniona += (s, e) => {
                Console.WriteLine("[LOGGER] Zdarzenie odnotowane w pliku logów.");
            };

            Console.WriteLine("Pierwsza zmiana ceny:");
            auto.Cena = 185000;

            // 3. ODSUBSKRYBOWANIE (-=)
            // Zapobiega "wiszącym referencjom" i wyciekom pamięci.
            auto.OnCenaZmieniona -= biuro.ReagujNaZmianeCeny;

            Console.WriteLine("\nDruga zmiana ceny (biuro już nie obserwuje):");
            auto.Cena = 170000;

            Console.WriteLine("\nZakończono kompletne laboratorium OOP C#.");
            Console.ReadKey();
        }
    }
}
                    
Wynik w konsoli
--- ZADANIE 10: Zdarzenia i subskrypcje --- Pierwsza zmiana ceny: [BIURO] Cena pojazdu Tesla Model 3 uległa zmianie! -> Stara: 200000 PLN, Nowa: 185000 PLN. [LOGGER] Zdarzenie odnotowane w pliku logów. Druga zmiana ceny (biuro już nie obserwuje): [LOGGER] Zdarzenie odnotowane w pliku logów. Zakończono kompletne laboratorium OOP C#.