Zaawansowane techniki obiektowe
Rok akademicki: 2026/27
Zadanie Laboratoryjne do Części 2: Architektura
Taktyczna Systemu Dowodzenia Flotą Orbitalną (Wzorce Strukturalne,
Kompozycja Kolekcji, Copy-on-Write i Autofac)
Prowadzący: Tomasz Goluch
Wersja: 1.0
Wymagany standard: C# 12 / .NET 8+
Maksymalna ocena zadania: 100 punktów (100%)
Forma realizacji: Kontynuacja projektu
OrbitalFleet z Części 1 (lub nowy moduł
taktyczny tworzony od zera)
1. Kontekst Biznesowy i Architektoniczny Projektu
W Części 1 zaprojektowałeś rdzeń kreacyjny silnika
OrbitalFleet.Engine, odpowiadający za
asynchroniczną produkcję okrętów w stoczniach, budowę Okrętu Flagowego
oraz wielowątkową replikację kwantową. Gdy flota znalazła się na orbicie
operacyjnej, dowództwo napotkało szereg krytycznych wyzwań
strukturalnych i pamięciowych:
- Integracja obcych i przestarzałych systemów radarowych: Przejęte stacje nasłuchowe generują surowe strumienie danych w niekompatybilnym formacie polarnym, które trzeba w locie tłumaczyć, deszyfrować, wzmacniać oraz chronić przed nieautoryzowanym odczytem.
- Eksplozja kombinatoryczna klas komunikacji i uzbrojenia: Próba połączenia \(N\) typów dyrektyw bojowych z \(M\) platformami transmisyjnymi za pomocą dziedziczenia doprowadziła do powstania \(N \times M\) trudnych w utrzymaniu podklas.
- Złożoność koordynacji uderzenia orbitalnego: Wyprowadzenie salwy wymaga ręcznej synchronizacji kilkunastu niskopoziomowych podsystemów (radarów, komputerów balistycznych, matryc energetycznych i transmiterów).
- Zarządzanie hierarchią formacji i topologią sieci przesyłowej: Rozkazy muszą być propagowane identycznie dla pojedynczego myśliwca, eskadry i całej armady, a łączenie węzłów sieci telemetrycznej nie może wymagać pisania \(N^2\) przeciążeń metod.
- Wyczerpanie pamięci RAM w pasie asteroid: Symulacja
sektora zawierającego \(100\,000\)
asteroid i dronów bojowych, z których każdy przechowuje ciężką siatkę
geometryczną 3D i widmo mineralne, powoduje natychmiastowy błąd
OutOfMemoryException.
Twoim zadaniem jest rozbudowa systemu o moduł taktyczny
wykorzystujący wszystkie wzorce strukturalne GoF,
strategię Copy-on-Write (CoW) oraz deklaratywne
mechanizmy strukturalne kontenera Autofac
(RegisterAdapter, RegisterDecorator,
RegisterComposite).
2. Inicjalizacja Środowiska (Krok 0: Rozbudowa Solucji lub Nowy Projekt)
Zadanie możesz zrealizować jako bezpośrednie rozszerzenie solucji
OrbitalFleet z Części 1 lub zainicjalizować czystą
strukturę projektową poniższymi poleceniami:
# Jeśli kontynuujesz projekt z Części 1, możesz od razu przejść do implementacji w OrbitalFleet.Core.
# Jeśli tworzysz czystą solucję pod Część 2:
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 STRUKTURALNA SYSTEMU TAKTYCZNEGO ORBITALFLEET │
├─────────────────────────────────────────────────────────────────────────────────┤
│ [M1: 22 pkt] Potok Sensorów ──► Adapter + Dekorator (OCP) + Proxy │
│ [M2: 18 pkt] Transmisja i Salwa ──► Most (Bridge N+M) + Fasada Uderzenia │
│ [M3: 20 pkt] Hierarchia i Topologia ──► Kompozyt + IEnumerable<T> ConnectTo │
│ [M4: 22 pkt] Symulacja Pasa Asteroid──► Pyłek (Flyweight) + Prototyp + CoW │
│ [M5: 18 pkt] Kontener IoC (Autofac) ──► RegisterAdapter / Decorator / Composite│
└─────────────────────────────────────────────────────────────────────────────────┘
Kamień Milowy 1: Potok Sensorów Dalekiego Zasięgu – Adapter, Dekorator i Pełnomocnik (22 pkt)
Flota przejęła starą stację nasłuchową operującą na klasie
LegacyDeepSpaceRadar (symulującej niezarządzany lub
przestarzały interfejs). Nowoczesny system kierowania ogniem wymaga
jednak asynchronicznego kontraktu ITacticalSensor. Ponadto
odczyty z sensora muszą być kaskadowo wzbogacane (deszyfrowane i
wzmacniane) oraz chronione przed nieuprawnionym dostępem.
Wymagania szczegółowe:
- [6 pkt] Adapter Obiektowy
(
LegacyRadarAdapter):- Klasa
LegacyDeepSpaceRadarudostępnia niekompatybilną metodęstring ReadRawPolarHexDump(int sectorCode)zwracającą surowy ciąg szesnastkowy (np."0xAF42:DIST=4500:AZIMUTH=180"). - Zdefiniuj nowoczesny interfejs
ITacticalSensorz metodąValueTask<SensorReading> ScanSectorAsync(string sectorName, CancellationToken ct = default)oraz rekordSensorReading(string Sector, double DistanceKm, string Payload, bool IsDecrypted, double SignalStrength). - Zaimplementuj
LegacyRadarAdapter : ITacticalSensor, który opakowujeLegacyDeepSpaceRadar, tłumaczy nazwę sektora na kod liczbowy i parsuje surowy zrzut na obiektSensorReading.
- Klasa
- [8 pkt] Kaskadowy Dekorator w duchu OCP
(
SensorDecoratorBase):- Utwórz klasę bazową dekoratora implementującą
ITacticalSensororaz dwa konkretne dekoratory:QuantumDecryptionDecorator– wywołuje opakowany sensor, a następnie dekodujePayload(np. dopisując znacznik[DECRYPTED-Q256]) i ustawia flagęIsDecrypted = true.SignalBoostDecorator– wzmacnia poleSignalStrengtho mnożnik wzmocnienia (np. \(\times 2.5\)) oraz formatuje raport wyjściowy.
- Dekoratory muszą umożliwiać dowolne kaskadowe składanie (tzw.
„cebulka”:
Boost(Decryption(Adapter(LegacyRadar)))).
- Utwórz klasę bazową dekoratora implementującą
- [8 pkt] Pełnomocnik Ochronny i Wirtualny
(
ClassifiedSensorProxy):- Zaimplementuj klasę
ClassifiedSensorProxy : ITacticalSensor, która pełni podwójną rolę:- Pełnomocnik Ochronny (Protection Proxy):
Sprawdza przekazany kontekst bezpieczeństwa dowódcy
(
SecurityClearanceLevel). Jeśli poziom jest niższy niżSecret, rzuca wyjątekUnauthorizedAccessExceptionbez wywoływania sensora docelowego. - Pełnomocnik Wirtualny (Virtual Proxy):
Przyjmuje w konstruktorze
Lazy<ITacticalSensor>(lubFunc<ITacticalSensor>) i odracza kosztowną alokację potoku sensora aż do momentu pierwszego pomyślnie autoryzowanego wywołaniaScanSectorAsync.
- Pełnomocnik Ochronny (Protection Proxy):
Sprawdza przekazany kontekst bezpieczeństwa dowódcy
(
- Zaimplementuj klasę
💡 Wskazówki implementacyjne: Adapter, Dekorator i Pełnomocnik (Rozwiń)
public enum SecurityClearanceLevel { Cadet, TacticalOfficer, Commander, Secret }
public record SensorReading(
string Sector,
double DistanceKm,
string Payload,
bool IsDecrypted = false,
double SignalStrength = 1.0);
public interface ITacticalSensor
{
ValueTask<SensorReading> ScanSectorAsync(string sectorName, CancellationToken ct = default);
}
// Niekompatybilny komponent (Adaptee)
public class LegacyDeepSpaceRadar
{
public string ReadRawPolarHexDump(int sectorCode)
=> $"HEX_SECTOR_{sectorCode}:DIST=1250:RAW_SIGNAL_ALPHA";
}
// 1. Adapter Obiektowy
public class LegacyRadarAdapter : ITacticalSensor
{
private readonly LegacyDeepSpaceRadar _legacyRadar;
public LegacyRadarAdapter(LegacyDeepSpaceRadar legacyRadar)
{
_legacyRadar = legacyRadar;
}
public ValueTask<SensorReading> ScanSectorAsync(string sectorName, CancellationToken ct = default)
{
int code = Math.Abs(sectorName.GetHashCode() % 1000);
string raw = _legacyRadar.ReadRawPolarHexDump(code);
var reading = new SensorReading(sectorName, 1250.0, raw, IsDecrypted: false, SignalStrength: 1.0);
return ValueTask.FromResult(reading);
}
}
// 2. Baza Dekoratora oraz Konkretny Dekorator
public abstract class SensorDecoratorBase : ITacticalSensor
{
protected readonly ITacticalSensor _innerSensor;
protected SensorDecoratorBase(ITacticalSensor innerSensor) => _innerSensor = innerSensor;
public virtual ValueTask<SensorReading> ScanSectorAsync(string sectorName, CancellationToken ct = default)
=> _innerSensor.ScanSectorAsync(sectorName, ct);
}
public class QuantumDecryptionDecorator : SensorDecoratorBase
{
public QuantumDecryptionDecorator(ITacticalSensor innerSensor) : base(innerSensor) { }
public override async ValueTask<SensorReading> ScanSectorAsync(string sectorName, CancellationToken ct = default)
{
var reading = await base.ScanSectorAsync(sectorName, ct);
return reading with
{
Payload = $"[DECRYPTED-Q256]({reading.Payload})",
IsDecrypted = true
};
}
}
// 3. Pełnomocnik Ochronny i Wirtualny (Protection + Virtual Proxy)
public class ClassifiedSensorProxy : ITacticalSensor
{
private readonly Lazy<ITacticalSensor> _lazyRealSensor;
private readonly SecurityClearanceLevel _currentClearance;
public bool IsRealSensorInitialized => _lazyRealSensor.IsValueCreated;
public ClassifiedSensorProxy(Func<ITacticalSensor> sensorFactory, SecurityClearanceLevel clearance)
{
_lazyRealSensor = new Lazy<ITacticalSensor>(sensorFactory, LazyThreadSafetyMode.ExecutionAndPublication);
_currentClearance = clearance;
}
public ValueTask<SensorReading> ScanSectorAsync(string sectorName, CancellationToken ct = default)
{
if (_currentClearance < SecurityClearanceLevel.Secret)
{
throw new UnauthorizedAccessException($"Odmowa dostępu! Ranga {_currentClearance} nie posiada autoryzacji Secret.");
}
// Inicjalizacja obiektu docelowego następuje dopiero tutaj!
return _lazyRealSensor.Value.ScanSectorAsync(sectorName, ct);
}
}
Kamień Milowy 2: Most Komunikacji Podprzestrzennej i Fasada Uderzenia Orbitalnego (18 pkt)
Rozkazy bojowe na orbicie ewoluują w dwóch niezależnych wymiarach: rodzaju dyrektywy (\(N\)) oraz fizycznego medium transmisji (\(M\)). Dodatkowo dowódca potrzebuje jednej, prostej metody Fasady, która skoordynuje skanowanie, obliczenia balistyczne i transmisję rozkazu.
Wymagania szczegółowe:
- [10 pkt] Wzorzec Most (
TacticalDirective\(\rightarrow\)ISubspaceTransmitter):- Implementor (
ISubspaceTransmitter): Interfejs definiujący niskopoziomową wysyłkę pakietu:Task<string> TransmitPacketAsync(string header, string payload, CancellationToken ct = default). - Konkretni Implementatorzy (\(M \ge
2\)):
QuantumEntanglementTransmitter(natychmiastowa transmisja splątana) orazHyperWaveRelayTransmitter(transmisja falowa dalekiego zasięgu). - Abstrakcja (
TacticalDirective): Klasa abstrakcyjna przyjmującaISubspaceTransmitterw konstruktorze (tworząc Most) i deklarująca metodęTask<string> BroadcastDirectiveAsync(string targetCoordinates, CancellationToken ct = default). - Udoskonalone Abstrakcje (\(N \ge
2\)):
OrbitalBombardmentDirectiveorazStealthReconDirective. Wykaż, że dowolną dyrektywę można połączyć z dowolnym transmiterem przy liczbie klas \(N + M\) zamiast \(N \times M\).
- Implementor (
- [8 pkt] Wzorzec Fasady
(
OrbitalStrikeFacade):- Zbuduj podsystemy pomocnicze (wstrzykiwane przez interfejsy zgodnie
z zasadą DIP):
IBallisticComputer(obliczający wektor trajektorii) orazIReactorPowerGrid(alokujący megawaty mocy do dział orbitalnych). - Zaimplementuj klasę
OrbitalStrikeFacade, która udostępnia prostą metodę wysokiego poziomu:Task<StrikeExecutionReport> ExecuteCoordinatedStrikeAsync(string sectorName, CancellationToken ct = default). - Fasada pod spodem: (1) skanuje sektor przez
ITacticalSensor, (2) weryfikuje zaszyfrowanie danych, (3) wylicza trajektorię wIBallisticComputer, (4) rezerwuje moc wIReactorPowerGrid, (5) rozgłasza rozkaz ogniowy przez Most (TacticalDirective) i zwraca zagregowany raport.
- Zbuduj podsystemy pomocnicze (wstrzykiwane przez interfejsy zgodnie
z zasadą DIP):
💡 Wskazówki implementacyjne: Most (Bridge) oraz Fasada (Rozwiń)
// 1. Implementor Mostu
public interface ISubspaceTransmitter
{
Task<string> TransmitPacketAsync(string header, string body, CancellationToken ct = default);
}
public class QuantumEntanglementTransmitter : ISubspaceTransmitter
{
public Task<string> TransmitPacketAsync(string header, string body, CancellationToken ct = default)
=> Task.FromResult($"[QUANTUM-LINK] {header} :: {body}");
}
public class HyperWaveRelayTransmitter : ISubspaceTransmitter
{
public Task<string> TransmitPacketAsync(string header, string body, CancellationToken ct = default)
=> Task.FromResult($"[HYPER-WAVE] {header} :: {body}");
}
// 2. Abstrakcja Mostu
public abstract class TacticalDirective
{
protected readonly ISubspaceTransmitter _transmitter;
protected TacticalDirective(ISubspaceTransmitter transmitter)
{
_transmitter = transmitter;
}
public abstract Task<string> BroadcastDirectiveAsync(string targetCoordinates, CancellationToken ct = default);
}
public class OrbitalBombardmentDirective : TacticalDirective
{
private readonly int _warheadYieldMegatons;
public OrbitalBombardmentDirective(ISubspaceTransmitter transmitter, int warheadYieldMegatons)
: base(transmitter)
{
_warheadYieldMegatons = warheadYieldMegatons;
}
public override Task<string> BroadcastDirectiveAsync(string targetCoordinates, CancellationToken ct = default)
=> _transmitter.TransmitPacketAsync("DIRECTIVE:BOMBARDMENT", $"TARGET={targetCoordinates};YIELD={_warheadYieldMegatons}MT", ct);
}
Kamień
Milowy 3: Hierarchia Dowodzenia i Polimorficzna Sieć Taktyczna –
Kompozyt, IEnumerable<T> i Metody Rozszerzające (20
pkt)
W tym kamieniu milowym zrealizujesz dwa komplementarne warianty
wzorca Kompozyt: klasyczne drzewo dowodzenia GoF oraz zaawansowany idiom
języka C# łączący IEnumerable<T> ze statycznymi
metodami rozszerzającymi.
Wymagania szczegółowe:
- [8 pkt] Drzewo Dowodzenia Flotą (Klasyczny Kompozyt
GoF):
- Zdefiniuj interfejs
IFleetUnitComponentz metodamistring GetUnitDesignation(),int CalculateTotalFirepower()orazvoid ExecuteManeuver(string formationName). - Zaimplementuj liść
StarfighterLeaf(pojedynczy okręt o określonej sile ognia) oraz kompozytSquadronComposite(mogący zawierać zarówno pojedyncze myśliwce, jak i podległe eskadry). - Wywołanie
CalculateTotalFirepower()na nadrzędnym kompozycie Armady musi rekurencyjnie zsumować siłę ognia wszystkich podległych liści i pod-eskadr w całym drzewie.
- Zdefiniuj interfejs
- [12 pkt] Polimorfizm Kolekcji
(
class RelayNode : IEnumerable<RelayNode>) i Eliminacja Eksplozji Kombinatorycznej (OCP):- Utwórz klasę pojedynczego węzła przekaźnikowego
TacticalRelayNode : IEnumerable<TacticalRelayNode>, posiadającą kolekcje połączeńICollection<TacticalRelayNode> IncomingLinksorazICollection<TacticalRelayNode> OutgoingLinks. - Zaimplementuj
GetEnumerator()z użyciemyield return this;, dzięki czemu pojedynczy węzeł zachowuje się jak jednoelementowa kolekcja. (Nie umieszczaj metody łączenia wewnątrz klasyTacticalRelayNode!). - Utwórz klasę grupy węzłów
RelayCluster : Collection<TacticalRelayNode>. - Zaimplementuj jedną statyczną metodę rozszerzającą
ConnectMeshTo(this IEnumerable<TacticalRelayNode> source, IEnumerable<TacticalRelayNode> target), która obsłuży wszystkie 4 kombinacje połączeń:TacticalRelayNode\(\rightarrow\)TacticalRelayNodeTacticalRelayNode\(\rightarrow\)RelayClusterRelayCluster\(\rightarrow\)TacticalRelayNodeRelayCluster\(\rightarrow\)RelayCluster
- Metoda musi chronić przed połączeniem obiektu z samym sobą
(
ReferenceEquals(source, target)orazReferenceEquals(from, to)).
- Utwórz klasę pojedynczego węzła przekaźnikowego
💡 Wskazówki implementacyjne: Idiom IEnumerable<T> i metoda rozszerzająca ConnectMeshTo (Rozwiń)
using System.Collections;
using System.Collections.ObjectModel;
public class TacticalRelayNode : IEnumerable<TacticalRelayNode>
{
public string NodeId { get; }
public ICollection<TacticalRelayNode> IncomingLinks { get; } = new HashSet<TacticalRelayNode>();
public ICollection<TacticalRelayNode> OutgoingLinks { get; } = new HashSet<TacticalRelayNode>();
public TacticalRelayNode(string nodeId) => NodeId = nodeId;
public IEnumerator<TacticalRelayNode> GetEnumerator()
{
yield return this;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public class RelayCluster : Collection<TacticalRelayNode>
{
public RelayCluster(string clusterPrefix, int count)
{
for (int i = 1; i <= count; i++)
{
Add(new TacticalRelayNode($"{clusterPrefix}-{i}"));
}
}
}
public static class TacticalMeshExtensions
{
public static void ConnectMeshTo(
this IEnumerable<TacticalRelayNode> source,
IEnumerable<TacticalRelayNode> target)
{
if (ReferenceEquals(source, target)) return;
foreach (var from in source)
{
foreach (var to in target)
{
if (!ReferenceEquals(from, to))
{
from.OutgoingLinks.Add(to);
to.IncomingLinks.Add(from);
}
}
}
}
}
Kamień Milowy 4: Pas Asteroid i Rój Dronów – Pyłek (Flyweight), Prototyp i Mechanizm Copy-on-Write (CoW) (22 pkt)
Sektor operacyjny składa się ze \(100\,000\) asteroid. Każda asteroida
należąca do określonego typu geologicznego posiada ciężkie dane
wewnętrzne (np. tablicę byte[] MeshVertices o rozmiarze
\(4\text{ KB}\), nazwę klasy mineralnej
oraz współczynnik odbicia światła Albedo). Tworzenie
osobnego bufora dla każdej asteroidy zajęłoby ponad \(400\text{ MB}\) pamięci RAM.
Wymagania szczegółowe:
- [8 pkt] Współdzielony Pyłek
(
AsteroidIntrinsicModel) i Bezpieczna Wątkowo Fabryka (AsteroidFlyweightFactory):- Utwórz klasę
AsteroidIntrinsicModelreprezentującą stan wewnętrzny (Intrinsic State):string MineralType,double StructuralIntegrity,string SurfaceState,byte[] HeavyMeshData. - Zaimplementuj fabrykę
AsteroidFlyweightFactorywykorzystującąConcurrentDictionary<string, AsteroidIntrinsicModel>, która gwarantuje, że dla danego klucza mineralnego w pamięci powstaje dokładnie jedna współdzielona instancja pyłka (nawet przy równoległym wywołaniu z wielu wątków). - Udostępnij właściwość
int CachedFlyweightsCount, zwracającą liczbę unikalnych modeli w pamięci podręcznej.
- Utwórz klasę
- [10 pkt] Kontekst Zewnętrzny i Strategia Copy-on-Write
(
AsteroidEntity):- Klasa
AsteroidEntityprzechowuje stan zewnętrzny (Extrinsic State): współrzędne \((X, Y, Z)\) oraz referencję doAsteroidIntrinsicModel. - Klasa
AsteroidIntrinsicModelmusi implementować wzorzec Prototyp (metodęAsteroidIntrinsicModel DeepClone(), kopiującą m.in. tablicęHeavyMeshData). - Mechanizm Copy-on-Write (CoW): Dopóki asteroidy są
tylko odczytywane, wszystkie egzemplarze tego samego typu wskazują
referencyjnie na ten sam obiekt pyłka
(
IsDetachedFromFlyweight == false). - Dodaj metodę mutującą
void ApplyThermalLaserHit(double damage, string newSurfaceState). W momencie jej wywołania pojedyncza trafiona asteroida musi automatycznie sklonować swój model przezDeepClone(), ustawićIsDetachedFromFlyweight = truei zmodyfikować wytrzymałość oraz stan powierzchni wyłącznie we własnej kopii, pozostawiając pozostałe \(99\,999\) asteroid nienaruszonymi!
- Klasa
- [4 pkt] Pomiar alokacji pamięci
(
GC.GetTotalMemory):- Zaimplementuj metodę diagnostyczną weryfikującą zużycie pamięci przy
alokacji \(100\,000\) obiektów
AsteroidEntitykorzystających z 3 typów pyłków w porównaniu do podejścia naiwnego (bez współdzielenia).
- Zaimplementuj metodę diagnostyczną weryfikującą zużycie pamięci przy
alokacji \(100\,000\) obiektów
💡 Wskazówki implementacyjne: Pyłek + Prototyp + Copy-on-Write (Rozwiń)
using System.Collections.Concurrent;
public class AsteroidIntrinsicModel
{
public string MineralType { get; set; }
public double StructuralIntegrity { get; set; }
public string SurfaceState { get; set; }
public byte[] HeavyMeshData { get; set; }
public AsteroidIntrinsicModel(string mineralType, double integrity, string surfaceState, int meshSizeBytes = 4096)
{
MineralType = mineralType;
StructuralIntegrity = integrity;
SurfaceState = surfaceState;
HeavyMeshData = new byte[meshSizeBytes];
}
// Wzorzec Prototyp wykorzystywany przez Copy-on-Write
public AsteroidIntrinsicModel DeepClone()
{
var copy = (AsteroidIntrinsicModel)this.MemberwiseClone();
copy.HeavyMeshData = (byte[])this.HeavyMeshData.Clone();
return copy;
}
}
public class AsteroidFlyweightFactory
{
private readonly ConcurrentDictionary<string, AsteroidIntrinsicModel> _cache = new();
public int CachedFlyweightsCount => _cache.Count;
public AsteroidIntrinsicModel GetOrAddModel(string mineralType, double defaultIntegrity = 100.0)
{
return _cache.GetOrAdd(mineralType, key => new AsteroidIntrinsicModel(key, defaultIntegrity, "Pristine"));
}
}
public class AsteroidEntity
{
public int X { get; }
public int Y { get; }
public int Z { get; }
public AsteroidIntrinsicModel Model { get; private set; }
public bool IsDetachedFromFlyweight { get; private set; }
public AsteroidEntity(int x, int y, int z, AsteroidIntrinsicModel sharedFlyweight)
{
(X, Y, Z) = (x, y, z);
Model = sharedFlyweight;
IsDetachedFromFlyweight = false;
}
// Strategia Copy-on-Write (CoW): klonowanie następuje wyłącznie przy zapisie/uszkodzeniu!
public void ApplyThermalLaserHit(double damage, string newSurfaceState)
{
if (!IsDetachedFromFlyweight)
{
Model = Model.DeepClone();
IsDetachedFromFlyweight = true;
}
Model.StructuralIntegrity -= damage;
Model.SurfaceState = newSurfaceState;
}
}
Kamień Milowy 5: Deklaratywna Orkiestracja Strukturalna w Kontenerze Autofac (18 pkt)
Wszystkie opracowane wzorce strukturalne należy połączyć w module konfiguracyjnym kontenera Autofac, wykorzystując jego natywne metody dedykowane wzorcom strukturalnym.
Wymagania szczegółowe:
- [8 pkt] Deklaratywna rejestracja Adaptera i Kaskady
Dekoratorów:
- Zarejestruj
LegacyDeepSpaceRadarw kontenerze. - Użyj metody
builder.RegisterAdapter<LegacyDeepSpaceRadar, ITacticalSensor>(radar => new LegacyRadarAdapter(radar))do automatycznej adaptacji interfejsu. - Użyj metody
builder.RegisterDecorator<QuantumDecryptionDecorator, ITacticalSensor>()orazbuilder.RegisterDecorator<SignalBoostDecorator, ITacticalSensor>(). Dowiedź, że rozwiązanie zależnościcontainer.Resolve<ITacticalSensor>()automatycznie zwraca kompletny, wielowarstwowo udekorowany adapter!
- Zarejestruj
- [6 pkt] Deklaratywna rejestracja Kompozytu
(
RegisterComposite) i Pyłka (SingleInstance):- Zdefiniuj interfejs ujścia telemetrii
ITelemetrySink(np.ConsoleTelemetrySink,BlackBoxTelemetrySink) oraz klasę kompozytuCompositeTelemetrySink : ITelemetrySinkprzyjmującąIEnumerable<ITelemetrySink>. - Zarejestruj kompozyt metodą
builder.RegisterComposite<CompositeTelemetrySink, ITelemetrySink>(), unikając błędu cyklicznej zależności. - Zarejestruj
AsteroidFlyweightFactoryjakoSingleInstance().
- Zdefiniuj interfejs ujścia telemetrii
- [4 pkt] Czysty Composition Root w
Program.cs:- Zaprezentuj w aplikacji konsolowej pełny scenariusz taktyczny
(skanowanie przez udekorowany adapter, wykonanie uderzenia przez Fasadę
i Most, kompozycję sieci przekaźników oraz symulację trafienia asteroidy
w modelu Copy-on-Write) bez użycia operatora
newdla serwisów poza konfiguracją kontenera.
- Zaprezentuj w aplikacji konsolowej pełny scenariusz taktyczny
(skanowanie przez udekorowany adapter, wykonanie uderzenia przez Fasadę
i Most, kompozycję sieci przekaźników oraz symulację trafienia asteroidy
w modelu Copy-on-Write) bez użycia operatora
💡 Wskazówki implementacyjne: Natywne rejestracje strukturalne w Autofac (Rozwiń)
using Autofac;
using OrbitalFleet.Core;
var builder = new ContainerBuilder();
// 1. Rejestracja obcego komponentu oraz deklaratywnego Adaptera:
builder.RegisterType<LegacyDeepSpaceRadar>();
builder.RegisterAdapter<LegacyDeepSpaceRadar, ITacticalSensor>(
legacy => new LegacyRadarAdapter(legacy));
// 2. Deklaratywne kaskadowe owijanie w Dekoratory (OCP):
builder.RegisterDecorator<QuantumDecryptionDecorator, ITacticalSensor>();
builder.RegisterDecorator<SignalBoostDecorator, ITacticalSensor>();
// 3. Rejestracja Kompozytu telemetrii bez ryzyka StackOverflowException:
builder.RegisterType<ConsoleTelemetrySink>().As<ITelemetrySink>();
builder.RegisterType<BlackBoxTelemetrySink>().As<ITelemetrySink>();
builder.RegisterComposite<CompositeTelemetrySink, ITelemetrySink>();
// 4. Fabryka Pyłków jako Singleton oraz Fasada Uderzenia:
builder.RegisterType<AsteroidFlyweightFactory>().SingleInstance();
builder.RegisterType<QuantumEntanglementTransmitter>().As<ISubspaceTransmitter>();
builder.RegisterType<BallisticComputer>().As<IBallisticComputer>();
builder.RegisterType<ReactorPowerGrid>().As<IReactorPowerGrid>();
builder.RegisterType<OrbitalStrikeFacade>();
using var container = builder.Build();
4. Wymagania Dotyczące Pakietu Testów Automatycznych
W projekcie OrbitalFleet.Tests zaimplementuj zestaw
testów jednostkowych i integracyjnych:
SensorPipelineTests(Adapter, Dekorator, Proxy):- Test weryfikujący poprawne tłumaczenie danych z
LegacyDeepSpaceRadarprzezLegacyRadarAdapteroraz kaskadowe działanie obu dekoratorów (IsDecrypted == trueoraz wzmocnionySignalStrength). - Test weryfikujący
ClassifiedSensorProxy: brak alokacji obiektu docelowego przed wywołaniem (IsRealSensorInitialized == false), rzucenieUnauthorizedAccessExceptiondla rangiCadet(nadalIsRealSensorInitialized == false) oraz poprawne wywołanie dla rangiSecret.
- Test weryfikujący poprawne tłumaczenie danych z
BridgeAndFacadeTests(Most i Fasada):- Test krzyżowy potwierdzający współpracę dowolnej dyrektywy
(
OrbitalBombardmentDirective,StealthReconDirective) z dowolnym transmiterem (QuantumEntanglementTransmitter,HyperWaveRelayTransmitter). - Test integracyjny
OrbitalStrikeFacade.ExecuteCoordinatedStrikeAsyncsprawdzający kompletną sekwencję wywołań podsystemów.
- Test krzyżowy potwierdzający współpracę dowolnej dyrektywy
(
CompositeAndMeshTests(Kompozyt iIEnumerable<T>):- Test hierarchii dowodzenia sprawdzający rekurencyjne sumowanie siły
ognia
CalculateTotalFirepower()w zagnieżdżonym drzewie eskadr. - Test sprawdzający wszystkie 4 warianty metody rozszerzającej
ConnectMeshTo(Węzeł–Węzeł, Węzeł–Klaster, Klaster–Węzeł, Klaster–Klaster) oraz blokadę pętli zwrotnej (node.ConnectMeshTo(node)).
- Test hierarchii dowodzenia sprawdzający rekurencyjne sumowanie siły
ognia
FlyweightCopyOnWriteTests(Pyłek, Prototyp, CoW i Pamięć):- Test tworzący \(100\,000\) obiektów
AsteroidEntityna bazie 3 typów mineralnych. Asercja potwierdzająca, żeCachedFlyweightsCount == 3, a referencjeReferenceEquals(a1.Model, a2.Model)dla tego samego minerału są identyczne. - Test strategii Copy-on-Write: wywołanie
ApplyThermalLaserHitna jednej asteroidzie musi zmienić jej flagęIsDetachedFromFlyweightnatrue, zmodyfikować jejStructuralIntegrity, a jednocześnie pozostawić referencję i wytrzymałość pozostałych \(99\,999\) asteroid całkowicie nietkniętą!
- Test tworzący \(100\,000\) obiektów
AutofacStructuralContainerTests:- Test potwierdzający, że
container.Resolve<ITacticalSensor>()zwraca obiekt owinięty kolejno wLegacyRadarAdapter\(\rightarrow\)QuantumDecryptionDecorator\(\rightarrow\)SignalBoostDecorator, acontainer.Resolve<ITelemetrySink>()zwracaCompositeTelemetrySinkagregujący wszystkie zarejestrowane ujścia.
- Test potwierdzający, że
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 | Adapter, Dekorator i Proxy | LegacyRadarAdapter, kaskada
dekoratorów w duchu OCP, ClassifiedSensorProxy (Protection
+ Virtual Proxy). |
22 pkt |
| 2 | Most (Bridge) i Fasada | Separacja wymiarów \(N \times M \rightarrow N + M\) w
komunikacji podprzestrzennej, orkiestracja w
OrbitalStrikeFacade. |
18 pkt |
| 3 | Kompozyt i Idiom
IEnumerable<T> |
Drzewo siły ognia floty, węzeł z
yield return this, jedna metoda rozszerzająca
ConnectMeshTo dla 4 relacji. |
20 pkt |
| 4 | Pyłek, Prototyp i Copy-on-Write | ConcurrentDictionary w
fabryce pyłków, \(100\,000\) asteroid w
RAM, izolacja mutacji przez CoW i DeepClone(). |
22 pkt |
| 5 | Strukturalny IoC / DI (Autofac) | Deklaratywne użycie
RegisterAdapter, RegisterDecorator,
RegisterComposite oraz SingleInstance(). |
18 pkt |
| SUMA | 100 pkt (100%) |