Zaawansowane techniki obiektowe

Rok akademicki: 2026/27
Zadanie Laboratoryjne do Części 3: Architektura Operacyjna i Zdarzeniowa Systemu Dowodzenia Flotą Orbitalną (Wzorce Czynnościowe, Weak Events, Paradygmat Funkcyjny, Saga i CQRS/Autofac)
Prowadzący: Tomasz Goluch
Wersja: 1.0 Wymagany standard: C# 12 / .NET 8+
Maksymalna ocena zadania: 100 punktów (100%)
Forma realizacji: Finałowa rozbudowa projektu OrbitalFleet z Części 1 i 2 (lub niezależny moduł operacyjny tworzony od zera)


1. Kontekst Biznesowy i Architektoniczny Projektu

W Części 1 zbudowałeś asynchroniczny rdzeń kreacyjny floty (OrbitalFleet.Engine), a w Części 2 rozwinąłeś jego warstwę strukturalną i optymalizację pamięciową (Copy-on-Write). W fazie aktywnych działań bojowych w głębokim kosmosie przed zespołem inżynierskim stanęły kluczowe problemy natury behawioralnej (operacyjnej):

  1. Sztywne potoki decyzyjne i algorytmy kierowania ogniem: Rozkazy taktyczne wymagają wieloetapowej autoryzacji wzdłuż łańcucha dowodzenia, szkielet procedury startowej musi pozwalać na dynamiczne wstrzykiwanie kroków funkcyjnych (Func<T>, Action), a algorytmy priorytetyzacji celów muszą być wymieniane w locie (runtime).
  2. Chaos warunkowy w obsłudze reaktora, brak historii nawigacji i awarie skoków nadprzestrzennych: Kod sterujący rdzeniem reaktora zamienił się w nieczytelne bloki switch/if, nawigatorzy nie potrafią cofnąć błędnych korekt trajektorii (Undo), a nieudany skok nadprzestrzenny pozostawia flotę z zablokowanymi zasobami bez procedury kompensacyjnej (Saga).
  3. Wycieki pamięci RAM na mostku bojowym (Lapsed Listener Problem): Krótkotrwałe panele taktyczne HUD subskrybują globalny strumień telemetrii za pomocą klasycznego słowa kluczowego event. Po zamknięciu panelu Garbage Collector nie jest w stanie zwolnić pamięci, co prowadzi do zawieszenia systemów pokładowych.
  4. Brak polimorficznej inspekcji floty i ewaluacji reguł otwarcia ognia: Statyczne metody rozszerzające nie radzą sobie z podwójnym wysłaniem (Double Dispatch) podczas audytu zróżnicowanego drzewa jednostek, a reguły otwarcia ognia (Rules of Engagement) są zaszyte na twardo w kodzie zamiast być interpretowane z drzewa wyrażeń logicznych.

Twoim zadaniem jest zaprojektowanie warstwy operacyjnej systemu OrbitalFleet wykorzystującej wzorce czynnościowe GoF, wzorzec Weak Event (WeakReference), Sagę kompensacyjną, RAII (IDisposable) oraz architekturę Mediator / CQRS spiętą kontenerem Autofac.


2. Inicjalizacja Środowiska (Krok 0: Rozbudowa Solucji lub Nowy Projekt)

Zadanie możesz zrealizować jako bezpośrednią kontynuację solucji OrbitalFleet z Części 1 i 2 lub utworzyć czystą solucję poniższymi poleceniami:

# Jeśli kontynuujesz projekt z Części 1 i 2, przejdź bezpośrednio do OrbitalFleet.Core.
# Jeśli inicjalizujesz czystą solucję pod Część 3:
dotnet new sln -n OrbitalFleet

dotnet new classlib -n OrbitalFleet.Core -f net8.0
dotnet new xunit -n OrbitalFleet.Tests -f net8.0
dotnet new console -n OrbitalFleet.App -f net8.0

dotnet sln add OrbitalFleet.Core/OrbitalFleet.Core.csproj
dotnet sln add OrbitalFleet.Tests/OrbitalFleet.Tests.csproj
dotnet sln add OrbitalFleet.App/OrbitalFleet.App.csproj

dotnet add OrbitalFleet.Tests/OrbitalFleet.Tests.csproj reference OrbitalFleet.Core/OrbitalFleet.Core.csproj
dotnet add OrbitalFleet.App/OrbitalFleet.App.csproj reference OrbitalFleet.Core/OrbitalFleet.Core.csproj

# Instalacja kontenera Autofac oraz biblioteki asercji FluentAssertions
dotnet add OrbitalFleet.Core/OrbitalFleet.Core.csproj package Autofac
dotnet add OrbitalFleet.App/OrbitalFleet.App.csproj package Autofac
dotnet add OrbitalFleet.Tests/OrbitalFleet.Tests.csproj package Autofac
dotnet add OrbitalFleet.Tests/OrbitalFleet.Tests.csproj package FluentAssertions

3. Szczegółowe Wymagania i Podział Punktów (Łącznie: 100 pkt)

┌─────────────────────────────────────────────────────────────────────────────────┐
│            ARCHITEKTURA OPERACYJNA SYSTEMU DOWODZENIA ORBITALFLEET              │
├─────────────────────────────────────────────────────────────────────────────────┤
│  [M1: 20 pkt] Potoki i Algorytmy      ──► Łańcuch + Template (OOP/Func) + Strat.│
│  [M2: 22 pkt] Stan, Historia i Saga   ──► MVVM Command + Stan + Memento + Saga  │
│  [M3: 20 pkt] Reaktywność i Pamięć GC ──► Obserwator (event) vs WeakReference   │
│  [M4: 22 pkt] Struktury i Gramatyki   ──► Iterator BFS/DFS + Visitor + Interpret│
│  [M5: 16 pkt] Mediator CQRS i Autofac ──► ITacticalMediator + RAII (IDisposable)│
└─────────────────────────────────────────────────────────────────────────────────┘

Kamień Milowy 1: Potok Autoryzacji Rozkazów, Funkcyjna Metoda Szablonowa i Wymienne Strategie Celowania (20 pkt)

Zanim jakikolwiek rozkaz bojowy trafi do wykonania, musi przejść przez łańcuch weryfikacyjny. Sam proces odpalenia uzbrojenia wymaga szkieletu algorytmu w dwóch wariantach (obiektowym i funkcyjnym), a komputer celowniczy musi wspierać wymienne algorytmy priorytetyzacji zagrożeń.

Wymagania szczegółowe:

  1. [8 pkt] Łańcuch Zobowiązań i Klasyczna Metoda Szablonowa (CommandAuthorizationHandler):
    • Zdefiniuj rekord żądania TacticalOrderRequest(int OrderId, string Category, int ClearanceLevel, string Payload) oraz abstrakcyjną klasę bazową CommandAuthorizationHandler.
    • W klasie bazowej zaimplementuj metodę sterującą string ProcessOrder(TacticalOrderRequest request) będącą Metodą Szablonową, która wywołuje metody chronione protected abstract bool CanHandle(TacticalOrderRequest request) oraz protected abstract string ExecuteHandling(TacticalOrderRequest request), a w razie braku obsługi przekazuje żądanie do _successor. Jeśli żaden obiekt w łańcuchu nie obsłuży żądania, zwracany jest komunikat [ALERT:UNHANDLED].
    • Zaimplementuj 3 konkretne ogniwa: RoutineNavigationHandler (Category == "Nav"), WeaponsReleaseHandler (Category == "Weapons" && ClearanceLevel >= 5) oraz SupremeOverrideHandler (ClearanceLevel >= 9).
  2. [6 pkt] Funkcyjna Metoda Szablonowa (Higher-Order Functions):
    • Utwórz klasę FunctionalLaunchPipeline z metodą: LaunchResult ExecuteLaunchSequence(Func<bool> preflightCheck, Func<string, double> trajectoryCalculator, Action<double> fireAction, string targetCode).
    • Metoda realizuje stały szkielet procedury ogniowej, wywołując wstrzyknięte delegaty jako zmienne kroki algorytmu bez konieczności tworzenia podklas.
  3. [6 pkt] Wzorzec Strategia (ITargetSelectionStrategy oraz IComparer<ThreatTarget>):
    • Zdefiniuj rekord ThreatTarget(string Id, double DistanceKm, double ShieldPower, int ThreatIndex) oraz interfejs ITargetSelectionStrategy (lub implementacje IComparer<ThreatTarget>).
    • Zaimplementuj dwie wymienne strategie wyboru celu głównego z kolekcji: ClosestTargetStrategy (najmniejszy dystans) oraz HighestThreatWeakestShieldStrategy (najwyższy indeks zagrożenia przy najniższych osłonach).
💡 Wskazówki implementacyjne: Łańcuch Zobowiązań, Funkcyjny Szablon i Strategia (Rozwiń)
public record TacticalOrderRequest(int OrderId, string Category, int ClearanceLevel, string Payload);

public abstract class CommandAuthorizationHandler
{
    private CommandAuthorizationHandler? _successor;

    public CommandAuthorizationHandler SetNext(CommandAuthorizationHandler next)
    {
        _successor = next;
        return next;
    }

    // Klasyczna Metoda Szablonowa sterująca przepływem w łańcuchu
    public string ProcessOrder(TacticalOrderRequest request)
    {
        if (CanHandle(request))
        {
            return ExecuteHandling(request);
        }
        if (_successor != null)
        {
            return _successor.ProcessOrder(request);
        }
        return $"[ALERT:UNHANDLED] Order {request.OrderId} ({request.Category}) rejected.";
    }

    protected abstract bool CanHandle(TacticalOrderRequest request);
    protected abstract string ExecuteHandling(TacticalOrderRequest request);
}

public class WeaponsReleaseHandler : CommandAuthorizationHandler
{
    protected override bool CanHandle(TacticalOrderRequest req)
        => req.Category == "Weapons" && req.ClearanceLevel >= 5;

    protected override string ExecuteHandling(TacticalOrderRequest req)
        => $"[AUTHORIZED:WEAPONS] Order {req.OrderId}: {req.Payload}";
}

// Funkcyjna Metoda Szablonowa (Higher-Order Functions)
public class FunctionalLaunchPipeline
{
    public bool ExecuteLaunchSequence(
        Func<bool> preflightCheck,
        Func<string, double> calculateVector,
        Action<double> commitLaunch,
        string targetDesignation)
    {
        if (!preflightCheck()) return false;
        double vector = calculateVector(targetDesignation);
        commitLaunch(vector);
        return true;
    }
}

Kamień Milowy 2: Konsola Mostka (MVVM ICommand), Maszyna Stanów Reaktora, Pamiątka (Memento) i Saga Kompensacyjna (22 pkt)

Mostek kapitański wymaga odseparowania logiki przycisków od interfejsu graficznego (wzorzec MVVM), deterministycznego zarządzania stanem rdzenia energetycznego, możliwości cofania skoków nawigacyjnych (Undo) oraz transakcyjnej koordynacji skoku nadprzestrzennego z obsługą błędów.

Wymagania szczegółowe:

  1. [5 pkt] Polecenie w architekturze MVVM (TacticalRelayCommand : ICommand):
    • Zaimplementuj klasę TacticalRelayCommand implementującą standardowy interfejs System.Windows.Input.ICommand, przyjmującą w konstruktorze Action<object?> execute oraz Predicate<object?>? canExecute = null.
    • Udostępnij czystą klasę BridgeConsoleViewModel (POCO), w której komenda aktywacji przeciążenia reaktora jest aktywna (CanExecute == true) wyłącznie wtedy, gdy reaktor nie znajduje się w stanie awaryjnej blokady.
  2. [6 pkt] Wzorzec Stan (IReactorCoreState):
    • Zaprojektuj obiekt kontekstu ReactorCoreContext oraz interfejs stanu IReactorCoreState z operacjami BoostPower(ReactorCoreContext ctx) oraz CoolDown(ReactorCoreContext ctx).
    • Zaimplementuj 3 klasy stanów: NominalCoreState (moc \(100\%\)), OverdriveCoreState (moc \(180\%\)) oraz EmergencyLockdownState (moc \(0\%\) – próba wywołania BoostPower w stanie OverdriveCoreState automatycznie przełącza reaktor w stan EmergencyLockdownState, z którego wyjść można tylko przez CoolDown). W klasie kontekstu nie wolno użyć ani jednej instrukcji if lub switch badającej aktualny stan!
  3. [5 pkt] Wzorzec Pamiątka (NavigationSnapshotMemento i FlightHistoryCaretaker):
    • Klasa StarshipNavigator (Originator) przechowuje aktualny sektor, wektor prędkości i zużyte paliwo.
    • Zaimplementuj niemutowalną pamiątkę NavigationSnapshotMemento oraz opiekuna FlightHistoryCaretaker opartego na stosie Stack<NavigationSnapshotMemento>, umożliwiającego wielokrotne wycofanie ostatnich korekt kursu (UndoLastJump()).
  4. [6 pkt] Wzorzec Saga z Akcjami Kompensacyjnymi (HyperspaceJumpSaga):
    • Skok nadprzestrzenny składa się z 3 rozproszonych kroków: (1) Rezerwacja deuteru z pomp paliwowych, (2) Naładowanie cewek skokowych, (3) Otwarcie tunelu podprzestrzennego.
    • Zaimplementuj koordynatora HyperspaceJumpSaga, który wykonuje kroki sekwencyjnie, rejestrując dla każdego zatwierdzonego kroku akcję kompensacyjną. Jeśli krok 3 rzuci wyjątek (np. GravityWellInterferenceException), Saga musi w odwrotnej kolejności (LIFO) wykonać kompensację kroku 2 (rozładowanie cewek) oraz kroku 1 (zwrot deuteru do zbiorników) i oznaczyć stan transakcji jako Compensated.
💡 Wskazówki implementacyjne: Stan, Pamiątka oraz Saga Kompensacyjna (Rozwiń)
// 1. Wzorzec Stan (State)
public interface IReactorCoreState
{
    string StateName { get; }
    int OutputPercentage { get; }
    void BoostPower(ReactorCoreContext context);
    void CoolDown(ReactorCoreContext context);
}

public class ReactorCoreContext
{
    public IReactorCoreState CurrentState { get; internal set; } = new NominalCoreState();
    public void RequestBoost() => CurrentState.BoostPower(this);
    public void RequestCoolDown() => CurrentState.CoolDown(this);
}

public class NominalCoreState : IReactorCoreState
{
    public string StateName => "NOMINAL";
    public int OutputPercentage => 100;
    public void BoostPower(ReactorCoreContext ctx) => ctx.CurrentState = new OverdriveCoreState();
    public void CoolDown(ReactorCoreContext ctx) { /* Już schłodzony */ }
}

public class OverdriveCoreState : IReactorCoreState
{
    public string StateName => "OVERDRIVE";
    public int OutputPercentage => 180;
    // Przegrzanie przy kolejnym podbiciu mocy -> przejście w Lockdown!
    public void BoostPower(ReactorCoreContext ctx) => ctx.CurrentState = new EmergencyLockdownState();
    public void CoolDown(ReactorCoreContext ctx) => ctx.CurrentState = new NominalCoreState();
}

public class EmergencyLockdownState : IReactorCoreState
{
    public string StateName => "LOCKDOWN";
    public int OutputPercentage => 0;
    public void BoostPower(ReactorCoreContext ctx)
        => throw new InvalidOperationException("Reaktor zablokowany! Wymagane pełne chłodzenie.");
    public void CoolDown(ReactorCoreContext ctx) => ctx.CurrentState = new NominalCoreState();
}

// 2. Wzorzec Saga z kompensacją LIFO
public class HyperspaceJumpSaga
{
    private readonly Stack<Func<Task>> _compensations = new();
    public List<string> ExecutionLog { get; } = new();

    public async Task<bool> ExecuteJumpTransactionAsync(
        Func<Task> reserveFuel, Func<Task> refundFuel,
        Func<Task> chargeCoils, Func<Task> dischargeCoils,
        Func<Task> openVortex)
    {
        try
        {
            await reserveFuel();
            ExecutionLog.Add("STEP1:FuelReserved");
            _compensations.Push(async () => { await refundFuel(); ExecutionLog.Add("COMP1:FuelRefunded"); });

            await chargeCoils();
            ExecutionLog.Add("STEP2:CoilsCharged");
            _compensations.Push(async () => { await dischargeCoils(); ExecutionLog.Add("COMP2:CoilsDischarged"); });

            await openVortex();
            ExecutionLog.Add("STEP3:VortexOpened");
            return true;
        }
        catch (Exception)
        {
            while (_compensations.TryPop(out var compensate))
            {
                await compensate();
            }
            return false;
        }
    }
}

Kamień Milowy 3: Telemetria Bojowa, Wycieki Pamięci (event) i Wzorzec Weak Event (WeakReference) (20 pkt)

Centralny nadajnik telemetrii (CombatTelemetryHub) działa przez cały czas życia procesu aplikacji (Singleton). Tymczasowe okna radarowe (TacticalHudOverlay) są otwierane i zamykane przez oficerów setki razy na minutę.

Wymagania szczegółowe:

  1. [6 pkt] Klasyczny Obserwator oparty na event i demonstracja wycieku pamięci (Lapsed Listener):
    • W klasie CombatTelemetryHub zdefiniuj standardowe zdarzenie public event EventHandler<TelemetryAlertEventArgs>? StandardAlertReceived;.
    • Utwórz klasę obserwatora TacticalHudOverlay subskrybującą to zdarzenie (hub.StandardAlertReceived += hud.OnAlert).
    • Wykaż za pomocą WeakReference, że po wyjściu zmiennej hud z zasięgu metody i wymuszeniu pełnego cyklu odśmiecania (GC.Collect(), GC.WaitForPendingFinalizers(), GC.Collect()), obiekt TacticalHudOverlay wciąż żyje w pamięci RAM (weakRef.IsAlive == true), ponieważ trzyma go silna referencja wewnątrz listy delegatów zdarzenia!
  2. [10 pkt] Bezpieczny Broker Słabych Zdarzeń (WeakTelemetryBroadcaster<TEventArgs>):
    • Zaimplementuj klasę WeakTelemetryBroadcaster<TEventArgs>, która przechowuje subskrybentów jako parę (WeakReference<object>? Target, MethodInfo Method).
    • Podczas publikacji zdarzenia (Publish(object sender, TEventArgs args)):
      • Jeśli Target.TryGetTarget(out var subscriber) zwraca true, metoda obsługi jest wywoływana na żywym obiekcie.
      • Jeśli TryGetTarget zwraca false (obiekt został usunięty przez Garbage Collector), martwy wpis jest automatycznie usuwany z wewnętrznej listy subskrybentów.
  3. [4 pkt] Test porównawczy odśmiecania pamięci (GC Memory Leak Verification):
    • Napisz deterministyczny test jednostkowy (z wydzieloną metodą pomocniczą [MethodImpl(MethodImplOptions.NoInlining)] alokującą obserwatora na stosie), który dowiedzie, że subskrybent podpięty przez WeakTelemetryBroadcaster zostaje poprawnie zwolniony przez GC (weakRef.IsAlive == false), a licznik aktywnych subskrybentów spada po kolejnej publikacji do zera.
💡 Wskazówki implementacyjne: Testowanie GC i WeakReference w .NET 8 (Rozwiń)

⚠️ Ważna wskazówka dotycząca kompilatora JIT i Garbage Collectora:
W trybie Debug kompilator JIT może przedłużać życie zmiennych lokalnych do końca całej metody testowej. Aby mieć 100% pewności, że lokalna referencja do obserwatora zniknie ze stosu przed wywołaniem GC.Collect(), tworzenie i subskrybowanie obserwatora zawsze wydzielaj do osobnej metody oznaczonej atrybutem [MethodImpl(MethodImplOptions.NoInlining)].

using System.Runtime.CompilerServices;

public class TelemetryAlertEventArgs(string Sector, string Message) : EventArgs
{
    public string Sector { get; } = Sector;
    public string Message { get; } = Message;
}

public class TacticalHudOverlay
{
    public int ReceivedAlertsCount { get; private set; }
    public void OnAlert(object? sender, TelemetryAlertEventArgs e) => ReceivedAlertsCount++;
}

// W teście jednostkowym xUnit:
[MethodImpl(MethodImplOptions.NoInlining)]
private static WeakReference SubscribeTemporaryHudWithWeakEvent(WeakTelemetryBroadcaster<TelemetryAlertEventArgs> broadcaster)
{
    var tempHud = new TacticalHudOverlay();
    broadcaster.Subscribe(tempHud.OnAlert);
    return new WeakReference(tempHud);
}

[Fact]
public void WeakEvent_Should_Allow_GarbageCollector_To_Reclaim_Observer()
{
    var broadcaster = new WeakTelemetryBroadcaster<TelemetryAlertEventArgs>();
    WeakReference hudWeakRef = SubscribeTemporaryHudWithWeakEvent(broadcaster);

    // Wymuszenie pełnego cyklu Garbage Collectora
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    // Obiekt obserwatora został pomyślnie usunięty z pamięci sterty!
    Assert.False(hudWeakRef.IsAlive);

    // Publikacja zdarzenia automatycznie czyści martwe wpisy
    broadcaster.Publish(this, new TelemetryAlertEventArgs("Alpha", "Ping"));
    Assert.Equal(0, broadcaster.ActiveSubscribersCount);
}

Kamień Milowy 4: Trawersowanie Drzewa Floty (Iterator BFS/DFS), Podwójne Wysłanie (Wizytator vs Metody Rozszerzające) i Interpreter Reguł (22 pkt)

Drzewo struktury organizacyjnej floty (zbudowane w Części 2) wymaga teraz zaawansowanych operacji behawioralnych: przechodzenia różnymi algorytmami kursora, polimorficznego audytu poszczególnych klas jednostek oraz automatycznego interpretowania reguł otwarcia ognia.

Wymagania szczegółowe:

  1. [6 pkt] Wzorzec Iterator dla struktur nieliniowych (TraverseBreadthFirst i TraverseDepthFirst):
    • Dla struktury drzewiastej jednostek floty (CommandTreeNode) zaimplementuj przy użyciu konstrukcji yield return dwa iteratory:
      1. BFS (Breadth-First Search): Przechodzący drzewo dowodzenia warstwami (Sztab \(\rightarrow\) Dowódcy Eskadr \(\rightarrow\) Pojedyncze Okręty) z użyciem kolejki Queue<CommandTreeNode>.
      2. DFS (Depth-First Search): Przechodzący drzewo w głąb każdej gałęzi z użyciem stosu Stack<CommandTreeNode>.
  2. [8 pkt] Wzorzec Odwiedzający / Wizytator (Double Dispatch) vs Statyczne Metody Rozszerzające:
    • Zdefiniuj hierarchię jednostek implementujących IVisitableFleetElement: InterceptorShip, MedicalFrigate oraz CommandCarrierComposite.
    • Dodaj metodę void Accept(IFleetElementVisitor visitor) realizującą Podwójne Wysłanie (Double Dispatch).
    • Zaimplementuj dwóch wizytatorów: CombatReadinessVisitor (wyliczający łączny indeks bojowy – inaczej dla myśliwca, inaczej dla fregaty medycznej, inaczej dla lotniskowca) oraz DiagnosticReportVisitor.
    • Wykaż w teście różnicę między wywołaniem element.Accept(visitor) (rozpoznającym rzeczywisty typ obiektu w runtime bez instrukcji if (x is InterceptorShip)) a przeciążoną statyczną metodą rozszerzającą wywołaną na zmiennej typu interfejsowego IVisitableFleetElement (wiązanie statyczne Single Dispatch w compile-time).
  3. [8 pkt] Wzorzec Interpreter dla Reguł Otwarcia Ognia (Rules of Engagement AST):
    • Zdefiniuj kontekst taktyczny TacticalBattleContext przechowujący zmienne logiczne (np. "HostileLocked", "ShieldsAbove50", "CivilianInSector") oraz interfejs wyrażenia IRuleExpression z metodą bool Evaluate(TacticalBattleContext context).
    • Zaimplementuj wyrażenie terminalne FactExpression(string factName) oraz wyrażenia nieterminalne: AndRuleExpression, OrRuleExpression oraz NotRuleExpression.
    • Zbuduj i zewaluuj złożone drzewo decyzyjne: \((\text{HostileLocked} \land \text{ShieldsAbove50}) \land \neg\text{CivilianInSector}\).
💡 Wskazówki implementacyjne: Wizytator (Double Dispatch) i Interpreter AST (Rozwiń)
// 1. Wizytator i Podwójne Wysłanie (Double Dispatch)
public interface IFleetElementVisitor
{
    void VisitInterceptor(InterceptorShip ship);
    void VisitMedicalFrigate(MedicalFrigate frigate);
    void VisitCarrier(CommandCarrierComposite carrier);
}

public interface IVisitableFleetElement
{
    string Designation { get; }
    void Accept(IFleetElementVisitor visitor);
}

public class InterceptorShip(string designation, intLaserCannons) : IVisitableFleetElement
{
    public string Designation { get; } = designation;
    public int LaserCannons { get; } = intLaserCannons;
    public void Accept(IFleetElementVisitor visitor) => visitor.VisitInterceptor(this);
}

public class MedicalFrigate(string designation, int医疗Bays) : IVisitableFleetElement
{
    public string Designation { get; } = designation;
    public int MedicalBays { get; } = 医疗Bays;
    public void Accept(IFleetElementVisitor visitor) => visitor.VisitMedicalFrigate(this);
}

// 2. Interpreter Reguł Taktycznych (AST)
public class TacticalBattleContext
{
    private readonly Dictionary<string, bool> _facts = new();
    public void SetFact(string key, bool value) => _facts[key] = value;
    public bool GetFact(string key) => _facts.TryGetValue(key, out bool val) && val;
}

public interface IRuleExpression
{
    bool Evaluate(TacticalBattleContext context);
}

public class FactExpression(string key) : IRuleExpression
{
    public bool Evaluate(TacticalBattleContext context) => context.GetFact(key);
}

public class AndRuleExpression(IRuleExpression left, IRuleExpression right) : IRuleExpression
{
    public bool Evaluate(TacticalBattleContext context) => left.Evaluate(context) && right.Evaluate(context);
}

public class NotRuleExpression(IRuleExpression inner) : IRuleExpression
{
    public bool Evaluate(TacticalBattleContext context) => !inner.Evaluate(context);
}

Kamień Milowy 5: Architektura CQS/CQRS z Mediatorem, RAII (IDisposable) i Orkiestracja w Kontenerze Autofac (16 pkt)

Wszystkie moduły behawioralne komunikują się na pokładzie Okrętu Flagowego za pośrednictwem centralnego Mediatora w architekturze CQS/CQRS, a zasoby sesyjne są deterministycznie zwalniane zgodnie z wzorcem RAII (IDisposable) przez zasięgi ILifetimeScope kontenera Autofac.

Wymagania szczegółowe:

  1. [6 pkt] Wzorzec Mediator w architekturze CQS/CQRS (ITacticalMediator):
    • Zdefiniuj generyczne kontrakty żądania i obsługi (wzorowane na bibliotece MediatR): ITacticalRequest<TResponse> oraz ITacticalRequestHandler<in TRequest, TResponse> where TRequest : ITacticalRequest<TResponse>.
    • Zaimplementuj klasę AutofacTacticalMediator : ITacticalMediator, która przy wywołaniu Task<TResponse> SendAsync<TResponse>(ITacticalRequest<TResponse> request, CancellationToken ct = default) dynamicznie rozwiązuje odpowiedni handler z bieżącego zasięgu ILifetimeScope i zwraca wynik.
    • Zaimplementuj co najmniej jedno zapytanie (Query, np. GetSectorThreatAssessmentQuery) oraz jedną komendę (Command, np. ExecuteEmergencyCoolDownCommand).
  2. [5 pkt] Wzorzec RAII / IDisposable (EncryptedCommsChannel):
    • Utwórz klasę zasobu sesyjnego EncryptedCommsChannel : IDisposable, która nabywa zasób w konstruktorze (IsChannelOpen = true), rzuca ObjectDisposedException przy próbie wywołania metody Transmit(string data) po zamknięciu oraz deterministycznie zwalnia zasób w metodzie Dispose() (IsChannelOpen = false).
  3. [5 pkt] Konfiguracja w Autofac i automatyczna utylizacja zasobów:
    • Zarejestruj w ContainerBuilder strategie celowania za pomocą klucza (Keyed<ITargetSelectionStrategy>), handlery CQRS, mediatora oraz zasób EncryptedCommsChannel z czasem życia InstancePerLifetimeScope().
    • Wykaż, że opuszczenie bloku using (var scope = container.BeginLifetimeScope()) automatycznie wywołuje Dispose() na pobranej z kontenera instancji EncryptedCommsChannel.
💡 Wskazówki implementacyjne: Mediator CQRS oparty na Autofac i RAII (Rozwiń)
using Autofac;

public interface ITacticalRequest<TResponse> { }

public interface ITacticalRequestHandler<in TRequest, TResponse>
    where TRequest : ITacticalRequest<TResponse>
{
    Task<TResponse> HandleAsync(TRequest request, CancellationToken ct = default);
}

public interface ITacticalMediator
{
    Task<TResponse> SendAsync<TResponse>(ITacticalRequest<TResponse> request, CancellationToken ct = default);
}

public class AutofacTacticalMediator : ITacticalMediator
{
    private readonly ILifetimeScope _scope;

    public AutofacTacticalMediator(ILifetimeScope scope) => _scope = scope;

    public Task<TResponse> SendAsync<TResponse>(ITacticalRequest<TResponse> request, CancellationToken ct = default)
    {
        var handlerType = typeof(ITacticalRequestHandler<,>).MakeGenericType(request.GetType(), typeof(TResponse));
        dynamic handler = _scope.Resolve(handlerType);
        return handler.HandleAsync((dynamic)request, ct);
    }
}

// Wzorzec RAII / IDisposable
public sealed class EncryptedCommsChannel : IDisposable
{
    public bool IsChannelOpen { get; private set; } = true;
    public bool IsDisposed { get; private set; } = false;

    public string SendEncryptedFrame(string frame)
    {
        ObjectDisposedException.ThrowIf(IsDisposed, this);
        return $"[CHANNEL-OK]:{frame}";
    }

    public void Dispose()
    {
        if (!IsDisposed)
        {
            IsChannelOpen = false;
            IsDisposed = true;
        }
    }
}

4. Wymagania Dotyczące Pakietu Testów Automatycznych

W projekcie OrbitalFleet.Tests przygotuj zestaw testów jednostkowych i integracyjnych weryfikujących wszystkie 5 kamieni milowych:

  1. PipelineAndStrategyTests (Łańcuch, Metoda Szablonowa i Strategia):
    • Test przepływu rozkazów przez CommandAuthorizationHandler (obsługa na odpowiednim szczeblu oraz zwrot [ALERT:UNHANDLED] dla nieznanego typu rozkazu).
    • Test funkcyjnej metody szablonowej FunctionalLaunchPipeline z wstrzykniętymi wyrażeniami lambda.
    • Test dynamicznej podmiany strategii celowania (ClosestTargetStrategy vs HighestThreatWeakestShieldStrategy).
  2. StateMementoAndSagaTests (MVVM Command, Stan, Pamiątka, Saga):
    • Test przejść maszyny stanów reaktora: Nominal \(\rightarrow\) Overdrive \(\rightarrow\) Lockdown (rzucenie wyjątku przy próbie BoostPower w stanie Lockdown oraz blokada CanExecute w TacticalRelayCommand).
    • Test zapisywania migawek nawigacyjnych i dwukrotnego cofnięcia stanu (UndoLastJump) przy użyciu wzorca Pamiątka.
    • Test Sagi Kompensacyjnej: zasymulowanie błędu otwarcia tunelu na 3. etapie i weryfikacja, że kroki kompensacyjne wykonały się w dokładnej kolejności LIFO (COMP2:CoilsDischarged \(\rightarrow\) COMP1:FuelRefunded).
  3. ObserverMemoryLeakTests (event vs WeakEvent):
    • Test dowodzący wycieku pamięci przy klasycznym event (weakRef.IsAlive == true po GC.Collect()).
    • Test dowodzący poprawnego zwolnienia pamięci obserwatora przy użyciu WeakTelemetryBroadcaster (weakRef.IsAlive == false po GC.Collect()) oraz automatycznego usunięcia martwej referencji.
  4. TraversalVisitorAndInterpreterTests (Iterator, Wizytator, Interpreter):
    • Test porównujący kolejność węzłów zwróconych przez iterator BFS oraz DFS na tym samym drzewie floty.
    • Test weryfikujący działanie Podwójnego Wysłania (Double Dispatch) we wzorcu Wizytatora na kolekcji IEnumerable<IVisitableFleetElement>.
    • Test ewaluacji drzewa wyrażeń logicznych Interpretera dla różnych kombinacji faktów w TacticalBattleContext.
  5. MediatorAndRaiiAutofacTests (Mediator CQRS i RAII w DI):
    • Test wysłania komendy i zapytania przez ITacticalMediator bez bezpośredniej znajomości klas obsługujących.
    • Test potwierdzający, że zamknięcie zasięgu ILifetimeScope w Autofac deterministycznie wywołuje Dispose() na obiekcie EncryptedCommsChannel, a kolejna próba wywołania metody na tym obiekcie zgłasza ObjectDisposedException.

Uruchomienie testów:

dotnet test --logger "console;verbosity=detailed"

5. Tabela Podsumowująca Oceny (Suma: 100 pkt / 100%)

Nr Zakres modułu Weryfikowane umiejętności, wzorce i mechanizmy Punkty
1 Łańcuch, Metoda Szablonowa i Strategia CommandAuthorizationHandler, funkcyjny szablon (Func/Action), wymienne strategie celowania. 20 pkt
2 MVVM Command, Stan, Pamiątka i Saga ICommand (CanExecute), bezwarunkowa maszyna stanów reaktora, Memento (Undo), kompensacja LIFO w Sadze. 22 pkt
3 Obserwator, GC i Weak Event Pattern Wykazanie wycieku pamięci dla event oraz jego eliminacja przez WeakReference i testy GC.Collect(). 20 pkt
4 Iterator, Wizytator i Interpreter Kursor BFS/DFS z yield return, Double Dispatch w Wizytatorze, ewaluacja drzewa AST w Interpreterze. 22 pkt
5 Mediator CQRS, RAII i Autofac ITacticalMediator oparty na ILifetimeScope, deterministyczne zwalnianie IDisposable (RAII). 16 pkt
SUMA 100 pkt (100%)