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):
- 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). - 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). - 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. - 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:
- [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 chronioneprotected abstract bool CanHandle(TacticalOrderRequest request)orazprotected 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) orazSupremeOverrideHandler(ClearanceLevel >= 9).
- Zdefiniuj rekord żądania
- [6 pkt] Funkcyjna Metoda Szablonowa (Higher-Order
Functions):
- Utwórz klasę
FunctionalLaunchPipelinez 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.
- Utwórz klasę
- [6 pkt] Wzorzec Strategia
(
ITargetSelectionStrategyorazIComparer<ThreatTarget>):- Zdefiniuj rekord
ThreatTarget(string Id, double DistanceKm, double ShieldPower, int ThreatIndex)oraz interfejsITargetSelectionStrategy(lub implementacjeIComparer<ThreatTarget>). - Zaimplementuj dwie wymienne strategie wyboru celu głównego z
kolekcji:
ClosestTargetStrategy(najmniejszy dystans) orazHighestThreatWeakestShieldStrategy(najwyższy indeks zagrożenia przy najniższych osłonach).
- Zdefiniuj rekord
💡 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:
- [5 pkt] Polecenie w architekturze MVVM
(
TacticalRelayCommand : ICommand):- Zaimplementuj klasę
TacticalRelayCommandimplementującą standardowy interfejsSystem.Windows.Input.ICommand, przyjmującą w konstruktorzeAction<object?> executeorazPredicate<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.
- Zaimplementuj klasę
- [6 pkt] Wzorzec Stan
(
IReactorCoreState):- Zaprojektuj obiekt kontekstu
ReactorCoreContextoraz interfejs stanuIReactorCoreStatez operacjamiBoostPower(ReactorCoreContext ctx)orazCoolDown(ReactorCoreContext ctx). - Zaimplementuj 3 klasy stanów:
NominalCoreState(moc \(100\%\)),OverdriveCoreState(moc \(180\%\)) orazEmergencyLockdownState(moc \(0\%\) – próba wywołaniaBoostPowerw stanieOverdriveCoreStateautomatycznie przełącza reaktor w stanEmergencyLockdownState, z którego wyjść można tylko przezCoolDown). W klasie kontekstu nie wolno użyć ani jednej instrukcjiiflubswitchbadającej aktualny stan!
- Zaprojektuj obiekt kontekstu
- [5 pkt] Wzorzec Pamiątka
(
NavigationSnapshotMementoiFlightHistoryCaretaker):- Klasa
StarshipNavigator(Originator) przechowuje aktualny sektor, wektor prędkości i zużyte paliwo. - Zaimplementuj niemutowalną pamiątkę
NavigationSnapshotMementooraz opiekunaFlightHistoryCaretakeropartego na stosieStack<NavigationSnapshotMemento>, umożliwiającego wielokrotne wycofanie ostatnich korekt kursu (UndoLastJump()).
- Klasa
- [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 jakoCompensated.
💡 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:
- [6 pkt] Klasyczny Obserwator oparty na
eventi demonstracja wycieku pamięci (Lapsed Listener):- W klasie
CombatTelemetryHubzdefiniuj standardowe zdarzeniepublic event EventHandler<TelemetryAlertEventArgs>? StandardAlertReceived;. - Utwórz klasę obserwatora
TacticalHudOverlaysubskrybującą to zdarzenie (hub.StandardAlertReceived += hud.OnAlert). - Wykaż za pomocą
WeakReference, że po wyjściu zmiennejhudz zasięgu metody i wymuszeniu pełnego cyklu odśmiecania (GC.Collect(),GC.WaitForPendingFinalizers(),GC.Collect()), obiektTacticalHudOverlaywciąż żyje w pamięci RAM (weakRef.IsAlive == true), ponieważ trzyma go silna referencja wewnątrz listy delegatów zdarzenia!
- W klasie
- [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)zwracatrue, metoda obsługi jest wywoływana na żywym obiekcie. - Jeśli
TryGetTargetzwracafalse(obiekt został usunięty przez Garbage Collector), martwy wpis jest automatycznie usuwany z wewnętrznej listy subskrybentów.
- Jeśli
- Zaimplementuj klasę
- [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 przezWeakTelemetryBroadcasterzostaje poprawnie zwolniony przez GC (weakRef.IsAlive == false), a licznik aktywnych subskrybentów spada po kolejnej publikacji do zera.
- Napisz deterministyczny test jednostkowy (z wydzieloną metodą
pomocniczą
💡 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łaniemGC.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:
- [6 pkt] Wzorzec Iterator dla struktur nieliniowych
(
TraverseBreadthFirstiTraverseDepthFirst):- Dla struktury drzewiastej jednostek floty
(
CommandTreeNode) zaimplementuj przy użyciu konstrukcjiyield returndwa iteratory:- BFS (Breadth-First Search): Przechodzący
drzewo dowodzenia warstwami (Sztab \(\rightarrow\) Dowódcy Eskadr \(\rightarrow\) Pojedyncze Okręty) z użyciem
kolejki
Queue<CommandTreeNode>. - DFS (Depth-First Search): Przechodzący
drzewo w głąb każdej gałęzi z użyciem stosu
Stack<CommandTreeNode>.
- BFS (Breadth-First Search): Przechodzący
drzewo dowodzenia warstwami (Sztab \(\rightarrow\) Dowódcy Eskadr \(\rightarrow\) Pojedyncze Okręty) z użyciem
kolejki
- Dla struktury drzewiastej jednostek floty
(
- [8 pkt] Wzorzec Odwiedzający / Wizytator (Double
Dispatch) vs Statyczne Metody Rozszerzające:
- Zdefiniuj hierarchię jednostek implementujących
IVisitableFleetElement:InterceptorShip,MedicalFrigateorazCommandCarrierComposite. - 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) orazDiagnosticReportVisitor. - Wykaż w teście różnicę między wywołaniem
element.Accept(visitor)(rozpoznającym rzeczywisty typ obiektu w runtime bez instrukcjiif (x is InterceptorShip)) a przeciążoną statyczną metodą rozszerzającą wywołaną na zmiennej typu interfejsowegoIVisitableFleetElement(wiązanie statyczne Single Dispatch w compile-time).
- Zdefiniuj hierarchię jednostek implementujących
- [8 pkt] Wzorzec Interpreter dla Reguł Otwarcia Ognia
(Rules of Engagement AST):
- Zdefiniuj kontekst taktyczny
TacticalBattleContextprzechowujący zmienne logiczne (np."HostileLocked","ShieldsAbove50","CivilianInSector") oraz interfejs wyrażeniaIRuleExpressionz metodąbool Evaluate(TacticalBattleContext context). - Zaimplementuj wyrażenie terminalne
FactExpression(string factName)oraz wyrażenia nieterminalne:AndRuleExpression,OrRuleExpressionorazNotRuleExpression. - Zbuduj i zewaluuj złożone drzewo decyzyjne: \((\text{HostileLocked} \land \text{ShieldsAbove50}) \land \neg\text{CivilianInSector}\).
- Zdefiniuj kontekst taktyczny
💡 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:
- [6 pkt] Wzorzec Mediator w architekturze CQS/CQRS
(
ITacticalMediator):- Zdefiniuj generyczne kontrakty żądania i obsługi (wzorowane na
bibliotece MediatR):
ITacticalRequest<TResponse>orazITacticalRequestHandler<in TRequest, TResponse> where TRequest : ITacticalRequest<TResponse>. - Zaimplementuj klasę
AutofacTacticalMediator : ITacticalMediator, która przy wywołaniuTask<TResponse> SendAsync<TResponse>(ITacticalRequest<TResponse> request, CancellationToken ct = default)dynamicznie rozwiązuje odpowiedni handler z bieżącego zasięguILifetimeScopei zwraca wynik. - Zaimplementuj co najmniej jedno zapytanie (Query, np.
GetSectorThreatAssessmentQuery) oraz jedną komendę (Command, np.ExecuteEmergencyCoolDownCommand).
- Zdefiniuj generyczne kontrakty żądania i obsługi (wzorowane na
bibliotece MediatR):
- [5 pkt] Wzorzec RAII /
IDisposable(EncryptedCommsChannel):- Utwórz klasę zasobu sesyjnego
EncryptedCommsChannel : IDisposable, która nabywa zasób w konstruktorze (IsChannelOpen = true), rzucaObjectDisposedExceptionprzy próbie wywołania metodyTransmit(string data)po zamknięciu oraz deterministycznie zwalnia zasób w metodzieDispose()(IsChannelOpen = false).
- Utwórz klasę zasobu sesyjnego
- [5 pkt] Konfiguracja w Autofac i automatyczna utylizacja
zasobów:
- Zarejestruj w
ContainerBuilderstrategie celowania za pomocą klucza (Keyed<ITargetSelectionStrategy>), handlery CQRS, mediatora oraz zasóbEncryptedCommsChannelz czasem życiaInstancePerLifetimeScope(). - Wykaż, że opuszczenie bloku
using (var scope = container.BeginLifetimeScope())automatycznie wywołujeDispose()na pobranej z kontenera instancjiEncryptedCommsChannel.
- Zarejestruj w
💡 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:
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
FunctionalLaunchPipelinez wstrzykniętymi wyrażeniami lambda. - Test dynamicznej podmiany strategii celowania
(
ClosestTargetStrategyvsHighestThreatWeakestShieldStrategy).
- Test przepływu rozkazów przez
StateMementoAndSagaTests(MVVM Command, Stan, Pamiątka, Saga):- Test przejść maszyny stanów reaktora:
Nominal\(\rightarrow\)Overdrive\(\rightarrow\)Lockdown(rzucenie wyjątku przy próbieBoostPowerw stanieLockdownoraz blokadaCanExecutewTacticalRelayCommand). - 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).
- Test przejść maszyny stanów reaktora:
ObserverMemoryLeakTests(eventvsWeakEvent):- Test dowodzący wycieku pamięci przy klasycznym
event(weakRef.IsAlive == truepoGC.Collect()). - Test dowodzący poprawnego zwolnienia pamięci obserwatora przy użyciu
WeakTelemetryBroadcaster(weakRef.IsAlive == falsepoGC.Collect()) oraz automatycznego usunięcia martwej referencji.
- Test dowodzący wycieku pamięci przy klasycznym
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.
MediatorAndRaiiAutofacTests(Mediator CQRS i RAII w DI):- Test wysłania komendy i zapytania przez
ITacticalMediatorbez bezpośredniej znajomości klas obsługujących. - Test potwierdzający, że zamknięcie zasięgu
ILifetimeScopew Autofac deterministycznie wywołujeDispose()na obiekcieEncryptedCommsChannel, a kolejna próba wywołania metody na tym obiekcie zgłaszaObjectDisposedException.
- Test wysłania komendy i zapytania przez
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%) |