01
Zadanie wprowadzające: System Rejestracji i Monitoringu Floty
Cel

Student demonstruje umiejętność projektowania klasy zawierającej różnorodne składowe, takie jak pola prywatne, publiczne właściwości oraz składowe statyczne. Zadanie pokazuje praktyczne zastosowanie konstruktorów instancyjnych i statycznych do zarządzania stanem obiektów oraz globalnymi licznikami systemowymi. Student rozumie różnicę między zachowaniem obiektowym a statycznym w kontekście platformy .NET.

Scenariusz

Pracujesz nad systemem dla firmy logistycznej, który ma za zadanie monitorować bazę pojazdów dostawczych. Każdy pojazd musi posiadać unikalny numer rejestracyjny, markę oraz informację o aktualnym stanie licznika kilometrów. System musi automatycznie zliczać, ile pojazdów zostało zarejestrowanych w trakcie działania aplikacji, korzystając z mechanizmów statycznych. Dodatkowo wymagane jest zdefiniowanie stałej określającej maksymalną dopuszczalną ładowność dla standardowej floty. Konstruktor statyczny powinien zostać użyty do zainicjalizowania globalnych parametrów konfiguracyjnych systemu, które są wspólne dla wszystkich obiektów. Każdy nowy pojazd jest tworzony z zerowym przebiegiem, chyba że podczas rejestracji zostanie podana inna wartość początkowa. Program musi uniemożliwić ustawienie ujemnego przebiegu poprzez odpowiednią logikę we właściwościach klasy. Na koniec aplikacja generuje raport wyświetlający dane konkretnego pojazdu oraz globalną liczbę wszystkich aut w bazie. Całość projektu musi być czytelna i stanowić solidny fundament pod dalszy rozwój modułu logistycznego. Jest to krytyczny element architektury pozwalający na zachowanie spójności danych o zasobach firmy.

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę o nazwie Pojazd w nowym pliku lub bezpośrednio w głównej przestrzeni nazw.
  2. Dodaj prywatne pole do przechowywania marki pojazdu oraz publiczną właściwość typu string.
  3. Zadeklaruj statyczne pole typu int, które będzie pełniło rolę licznika wszystkich utworzonych obiektów.
  4. Dodaj stałą (const) określającą domyślną wersję oprogramowania sterownika pojazdu.
  5. Zaimplementuj właściwość Przebieg z jawnym polem bazowym i walidacją uniemożliwiającą ustawienie wartości mniejszej od zera.
  6. Stwórz konstruktor statyczny, który wypisze w konsoli komunikat o inicjalizacji systemu floty.
  7. Dodaj konstruktor instancyjny przyjmujący markę i numer rejestracyjny, który inkrementuje statyczny licznik.
  8. Uzupełnij kod o konstruktor przeciążony, pozwalający ustawić również początkowy przebieg przy tworzeniu obiektu.
  9. W metodzie Main utwórz co najmniej dwa obiekty klasy Pojazd z różnymi danymi wejściowymi.
  10. Wyświetl stan licznika globalnego oraz szczegółowe informacje o każdym pojeździe, korzystając z dostępu do składowych.
Kod rozwiązania
using System;

class Pojazd
{
    // Pola i stałe
    public const string WersjaSystemu = "2.0.4-LTS";
    private static int licznikPojazdow;
    private decimal przebieg;

    // Auto-właściwości
    public string Marka { get; set; }
    public string Rejestracja { get; set; }

    // Właściwość z logiką walidacji
    public decimal Przebieg
    {
        get => przebieg;
        set
        {
            if (value >= 0) przebieg = value;
            else Console.WriteLine("Błąd: Przebieg nie może być ujemny!");
        }
    }

    // Składowa statyczna - dostępna dla całej klasy
    public static int Licznik => licznikPojazdow;

    // Konstruktor statyczny - wywoływany raz przy pierwszym użyciu klasy
    static Pojazd()
    {
        licznikPojazdow = 0;
        Console.WriteLine("System floty: zainicjalizowano składowe statyczne.");
    }

    // Konstruktor instancyjny
    public Pojazd(string marka, string rejestracja)
    {
        Marka = marka;
        Rejestracja = rejestracja;
        licznikPojazdow++;
    }

    // Przeciążony konstruktor
    public Pojazd(string marka, string rejestracja, decimal przebiegStart) : this(marka, rejestracja)
    {
        Przebieg = przebiegStart;
    }
}

class Program
{
    static void Main()
    {
        Console.WriteLine($"Wersja oprogramowania: {Pojazd.WersjaSystemu}");

        Pojazd p1 = new Pojazd("Ford", "KR12345");
        Pojazd p2 = new Pojazd("Iveco", "WA98765", 1500.50m);

        Console.WriteLine($"Zarejestrowano {Pojazd.Licznik} pojazdy.");
        Console.WriteLine($"Pojazd 1: {p1.Marka}, Przebieg: {p1.Przebieg} km");
        Console.WriteLine($"Pojazd 2: {p2.Marka}, Przebieg: {p2.Przebieg} km");
    }
}
                    
2.1
Modelowanie geometryczne: Klasa Prostokat
Cel

Zapoznanie studentów z podstawami tworzenia własnych typów danych w C#. Głównym celem jest zrozumienie relacji między polami klasy a jej zachowaniem (metodami) oraz opanowanie operacji na polach prywatnych.

Scenariusz

Zlecono Ci stworzenie modułu do przeliczeń geometrycznych dla aplikacji projektowej. Musisz zdefiniować klasę o nazwie "Prostokat", która będzie w stanie przechowywać długości dwóch boków. Obiekt tej klasy powinien być odpowiedzialny za własne obliczenia, takie jak wyznaczanie pola powierzchni oraz obwodu figury. Pola przechowujące wymiary muszą być ukryte przed bezpośrednim dostępem z zewnątrz, aby zapewnić integralność danych (brak możliwości ustawienia boków o długości zero lub ujemnej bez kontroli). Program główny powinien umożliwiać utworzenie kilku różnych prostokątów o wymiarach podanych przez użytkownika w konsoli. Po wprowadzeniu danych, system ma wyświetlić kompletny zestaw informacji dla każdej z figur w czytelny sposób. Implementacja tej klasy jest wstępem do budowania bardziej złożonych silników rysujących i obliczeniowych w przyszłych modułach. Musisz wykazać, że Twoja klasa poprawnie zarządza swoimi właściwościami wewnętrznymi. Program powinien kończyć się podsumowaniem, w którym porównasz pola powierzchni dwóch utworzonych prostokątów. Jest to klasyczny przykład enkapsulacji logiki i danych w pojedynczym komponencie.

Sugerowane kroki do wykonania
  1. Stwórz nową klasę o nazwie Prostokat.
  2. Dodaj dwa prywatne pola typu double: bokA i bokB.
  3. Zaimplementuj publiczne metody SetBoki(double a, double b) do ustawiania wymiarów.
  4. Wewnątrz metody ustawiającej dodaj warunek if, blokujący przypisanie wartości mniejszych lub równych 0.
  5. Dodaj publiczną metodę ObliczPole(), która zwraca iloczyn boków.
  6. Zaimplementuj metodę ObliczObwod(), zwracającą sumę długości wszystkich boków.
  7. W metodzie Main utwórz instancję klasy za pomocą słowa kluczowego new.
  8. Pobierz wymiary od użytkownika i przekaż je do obiektu za pomocą metody SetBoki.
  9. Wywołaj metody obliczeniowe i wyświetl wyniki na konsoli.
  10. Spróbuj celowo ustawić niepoprawne dane, aby sprawdzić odporność Twojej klasy na błędy.
2.2
Bankowość: Enkapsulacja Konta Bankowego
Cel

Opanowanie praktycznego zastosowania właściwości (properties) zamiast bezpośredniego dostępu do pól. Student uczy się implementować logikę biznesową wewnątrz akcesorów get oraz set, zapewniając bezpieczeństwo transakcyjne.

Scenariusz

Budujesz uproszczony system do obsługi platformy bankowości elektronicznej. Twoim zadaniem jest stworzenie klasy "KontoBankowe", która bezpiecznie przechowuje numer konta (tylko do odczytu po utworzeniu) oraz aktualne saldo (dostępne do odczytu, ale chronione przed bezpośrednią modyfikacją). Wszystkie operacje finansowe, takie jak wpłaty i wypłaty środków, muszą odbywać się za pośrednictwem dedykowanych metod, które sprawdzają poprawność transakcji. Na przykład, system nie może pozwolić na wypłacenie kwoty większej niż aktualnie dostępne środki na koncie. Klasa powinna również posiadać właściwość określającą limit debetowy, który jest stały dla wszystkich kont typu podstawowego. Użytkownik programu w konsoli powinien móc wykonać serię operacji: sprawdzić stan konta, wpłacić pieniądze oraz spróbować wypłacić kwotę przekraczającą stan posiadania, aby przetestować systemy obronne. Program musi generować czytelne komunikaty o sukcesie lub przyczynie odrzucenia transakcji. Dzięki temu podejściu dane finansowe klienta pozostają spójne i chronione przed nieautoryzowaną zmianą. Finalnie system powinien pokazać historię operacji w skróconej formie lub aktualne podsumowanie po serii ruchów finansowych.

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę KontoBankowe z prywatnym polem saldo.
  2. Dodaj właściwość NumerKonta z modyfikatorem init lub ustawianą tylko w konstruktorze.
  3. Zaimplementuj właściwość publiczną Saldo, która posiada tylko akcesor get.
  4. Utwórz publiczną metodę Wplata(decimal kwota), która powiększa saldo tylko przy kwotach dodatnich.
  5. Wprowadź metodę Wyplata(decimal kwota) z logiką sprawdzającą, czy kwota jest mniejsza lub równa saldu.
  6. Dodaj do klasy właściwość Wlasciciel korzystając z auto-właściwości { get; set; }.
  7. W Main utwórz obiekt konta i nadaj mu dane właściciela.
  8. Wykonaj testową wpłatę 1000 zł i wyświetl saldo.
  9. Spróbuj wypłacić 1500 zł i upewnij się, że system zablokował tę operację.
  10. Wyświetl komunikat końcowy z danymi konta przy użyciu formatowania walutowego.
2.3
Narzędzia Matematyczne: Klasa Statyczna
Cel

Zrozumienie koncepcji klas statycznych w C# oraz sytuacji, w których ich użycie jest uzasadnione. Student uczy się projektować biblioteki metod pomocniczych, które nie wymagają tworzenia instancji obiektu do działania.

Scenariusz

Wyobraź sobie, że piszesz uniwersalną bibliotekę narzędziową dla naukowców, która zawiera przydatne konwertery jednostek miar. Zamiast tworzyć za każdym razem nowy obiekt kalkulatora, chcesz, aby metody były dostępne globalnie w całym programie. Twoim zadaniem jest stworzenie klasy statycznej o nazwie "Konwerter", która będzie posiadać metody do zamiany stopni Celsjusza na Fahrenheita oraz kilometrów na mile morskie. Klasa ta powinna również przechowywać publiczne, statyczne stałe, takie jak wartość liczby PI lub przelicznik mili. Użytkownik programu powinien mieć możliwość szybkiego wywołania tych metod bezpośrednio po nazwie klasy w metodzie Main. Program musi pobrać dane wejściowe od użytkownika, wykonać obliczenia za pomocą Twojej statycznej biblioteki i zwrócić wynik z precyzją naukową. Ważne jest, aby zrozumieć, że klasa statyczna nie może posiadać konstruktora instancyjnego ani zwykłych pól nie-statycznych. Implementacja tego zadania uczy poprawnych wzorców projektowych stosowanych przy budowie tzw. "utility classes" w środowisku .NET. Całość powinna być zorganizowana w prosty i logiczny sposób, ułatwiający reużywalność kodu w innych projektach.

Sugerowane kroki do wykonania
  1. Zadeklaruj klasę z modyfikatorem static o nazwie Konwerter.
  2. Dodaj publiczne, statyczne pole stałe (const) dla przelicznika mil (np. 0.621).
  3. Zaimplementuj statyczną metodę CelsjuszToFahrenheit(double c), zwracającą wynik double.
  4. Dodaj analogiczną metodę do konwersji kilometrów na mile.
  5. Upewnij się, że wszystkie metody wewnątrz klasy również posiadają modyfikator static.
  6. W programie głównym spróbuj zaalokować obiekt tej klasy (sprawdź, czy kompilator na to pozwoli).
  7. Wywołaj metody konwersji używając składni Konwerter.Metoda(...).
  8. Pobierz od użytkownika wartość w kilometrach i wyświetl wynik przeliczenia na mile.
  9. Przetestuj konwersję temperatury dla punktu zamarzania i wrzenia wody.
  10. Wyświetl komunikat końcowy informujący o zakończeniu korzystania z narzędzi statycznych.
2.4
Kadry: Zarządzanie Danymi Pracownika (Konstruktory)
Cel

Opanowanie mechanizmu przeciążania konstruktorów oraz efektywnego wykorzystania słowa kluczowego this do unikania powielania kodu inicjalizacyjnego. Student uczy się poprawnie zarządzać stanem początkowym obiektów klasy.

Scenariusz

System HR nowo założonego startupu wymaga elastycznego sposobu dodawania nowych pracowników do bazy danych. Nie wszyscy pracownicy mają od razu przypisane stanowisko czy określone wynagrodzenie w momencie podpisywania wstępnych dokumentów. Musisz zaprojektować klasę "Pracownik", która pozwala na utworzenie obiektu na trzy różne sposoby. Pierwszy wariant wymaga podania tylko imienia i nazwiska (wynagrodzenie ustawiane jest na płacę minimalną). Drugi wariant pozwala podać imię, nazwisko oraz konkretne wynagrodzenie, jeśli jest ono znane od początku. Trzeci, najbardziej rozbudowany konstruktor, przyjmuje wszystkie dane wraz z nazwą działu, w którym osoba będzie pracować. Twoim celem jest połączenie tych konstruktorów w taki sposób, aby podstawowe przypisania działy się w jednym miejscu, a inne konstruktory jedynie „dopełniały” dane (tzw. constructor chaining). Program w konsoli powinien utworzyć trzech różnych pracowników, korzystając z każdego z dostępnych konstruktorów, a następnie wyświetlić ich dane jako sformatowane karty pracownicze. To zadanie uczy, jak projektować API klas, które jest przyjazne dla innych programistów i odporne na brakujące dane startowe.

Sugerowane kroki do wykonania
  1. Zdefiniuj klasę Pracownik z odpowiednimi polami instancyjnymi.
  2. Utwórz najbardziej ogólny konstruktor przyjmujący wszystkie parametry (Imię, Nazwisko, Pensja, Dzial).
  3. Dodaj konstruktor przyjmujący Imię i Nazwisko, który przekazuje resztę danych do głównego konstruktora za pomocą : this(...).
  4. Implementując : this(...), ustaw domyślną pensję na np. 4000 a dział na "Nieprzypisany".
  5. Dodaj wersję pośrednią konstruktora przyjmującą Imię, Nazwisko i Pensję.
  6. Wewnątrz klasy stwórz metodę PokazDane(), która wypisze czytelny raport o osobie.
  7. W Main utwórz obiekt „p1” podając tylko dane osobowe.
  8. Utwórz obiekt „p2” z pełnymi danymi (np. Jan Kowalski, 8000, IT).
  9. Wywołaj PokazDane dla wszystkich rekordów w bazie.
  10. Zwróć uwagę na oszczędność kodu dzięki kaskadowemu wywoływaniu konstruktorów.
2.5
Konfiguracja Systemu: Konstruktor Statyczny
Cel

Zrozumienie specjalnej roli konstruktora statycznego w inicjalizacji środowiska uruchomieniowego aplikacji. Student uczy się odróżniać inicjalizację danych wspólnych (klasowych) od danych specyficznych dla konkretnych instancji.

Scenariusz

Budujesz zaawansowany "SystemKonfiguracyjny" dla aplikacji serwerowej, która przy starcie musi załadować pewne parametry globalne, takie jak adres serwera bazy danych oraz aktualna strefa czasowa. Te parametry są identyczne dla każdego modułu systemu i nie zmieniają się w trakcie jego działania. Twoim zadaniem jest stworzenie klasy, która przy pierwszej próbie użycia (np. odczytu dowolnego parametru) automatycznie wykona jednorazową konfigurację za pomocą konstruktora statycznego. Konstruktor ten ma symulować odczyt danych z pliku (poprzez wyświetlenie komunikatu w konsoli) i zainicjalizować statyczne pola klasy. Dodatkowo klasa powinna pozwalać na tworzenie obiektów reprezentujących konkretne sesje użytkowników, które posiadają własne, unikalne identyfikatory. Musisz wykazać, że moment wywołania konstruktora statycznego jest niezależny od tworzenia poszczególnych obiektów tej klasy. Wyświetl logi systemowe tak, aby było widać kolejność: najpierw inicjalizacja globalna, potem start konkretnych sesji. To zadanie ma kluczowe znaczenie dla zrozumienia cyklu życia aplikacji w modelu pamięciowym .NET. Program kończy się wyświetleniem parametrów, które zostały poprawnie załadowane tylko jeden raz.

Sugerowane kroki do wykonania
  1. Stwórz klasę SystemKonfiguracyjny (nie musi być statyczna jako całość).
  2. Dodaj statyczne pole string AdresSerwera oraz statyczną właściwość DataStartu.
  3. Zaimplementuj konstruktor statyczny (bez parametrów i modyfikatorów dostępu!).
  4. Wewnątrz konstruktora statycznego przypisz adres serwera i zrób Console.WriteLine logu.
  5. Dodaj zwykły konstruktor instancyjny, który przypisuje unikalny ID sesji.
  6. W Main wypisz najpierw statyczną stałą lub właściwość klasy.
  7. Zauważ, że konstruktor statyczny uruchomił się samoistnie przed wypisaniem danych.
  8. Utwórz dwa obiekty sesji i zobacz, czy konstruktor statyczny wywoła się ponownie (nie powinien).
  9. Wypisz stan wszystkich składowych dla obu sesji.
  10. Podsumuj działanie mechanizmu statycznego odpowiednim komunikatem.
2.6
IoT: Cyfrowy Termometr z Historią (Właściwości)
Cel

Integracja wiedzy o właściwościach, tablicach oraz logice wewnątrz akcesorów set. Student uczy się projektowania obiektów, które nie tylko przechowują stan, ale również reagują na zmiany tego stanu w sposób inteligentny.

Scenariusz

Jesteś inżynierem oprogramowania projektującym inteligentny termometr domowy (IoT). Urządzenie posiada klasę "Termometr", która przechowuje aktualną temperaturę, ale system wymaga, aby każda zmiana tej wartości była automatycznie zapisywana jako najwyższa zanotowana temperatura (rekord), jeśli nowa wartość jest większa od poprzedniej. Musisz wykorzystać właściwość C# do implementacji tej logiki: przy każdym przypisaniu nowej temperatury, program sprawdza, czy nie pobito rekordu. Dodatkowo termometr powinien informować, jeśli nowa temperatura przekracza bezpieczny zakres (np. powyżej 40 stopni Celsjusza) – informacja taka powinna być wypisana w konsoli jako ostrzeżenie. Użytkownik w pętli symuluje odczyty z sensora w ciągu godziny. Twoim zadaniem jest zapewnienie dodatkowej właściwości o nazwie "Srednia", która jest obliczana na bieżąco (lub po zakończeniu sesji) jako średnia z ostatnich trzech pomiarów zachowanych w małej tablicy wewnętrznej. To zadanie łączy wiedzę o enkapsulacji z prostymi algorytmami monitoringu procesów fizycznych. Finalny raport prezentuje aktualny odczyt, rekord historyczny oraz status bezpieczeństwa urządzenia. Program kończy się, gdy użytkownik wprowadzi specjalny kod wyłączający symulację sensora.

Sugerowane kroki do wykonania
  1. Stwórz klasę Termometr z prywatnym polem aktualnaTemp oraz rekord.
  2. Zaimplementuj właściwość AktualnaTemperatura z pełnym ciałem akcesora set.
  3. W akcesorze set dodaj warunek: jeśli value > rekord, to rekord = value.
  4. Dopisz ostrzeżenie w konsoli wewnątrz właściwości, jeśli temperatura przekroczy 40.0.
  5. Dodaj publiczną właściwość tylko do odczytu (get) dla pola rekord.
  6. W Main utwórz instancję termometru.
  7. Uruchom pętlę pobierającą 5 pomiarów od użytkownika w jednej sesji.
  8. Po każdym wprowadzeniu danych, przypisz je do właściwości AktualnaTemperatura.
  9. Wyświetl bieżącą wartość oraz aktualnie najwyższy wynik (rekord).
  10. Upewnij się, że Twoja klasa poprawnie separuje logikę rekordów od zwykłego przechowywania danych.