02
Hermetyzacja i właściwości: ochrona stanu obiektu
Cel zadania

Nauka techniki hermetyzacji (enkapsulacji) danych poprzez ukrywanie pól klasy i udostępnianie ich za pomocą właściwości (properties). Student dowie się, jak walidować dane wejściowe, korzystać z akcesorów get/set oraz tworzyć pola tylko do odczytu.

Scenariusz problemowy

Po incydentach z błędnymi danymi w komisie "Auto-Premium", decydujesz się na wprowadzenie rygorystycznych zasad. System nie może już przyjmować ujemnych lat produkcji ani pustych nazw marek. Jako główny architekt rozwiązujesz ten problem, zmieniając wszystkie publiczne pola klasy "Samochod" na prywatne. Zewnętrzny świat nie ma już do nich bezpośredniego dostępu.

W zamian wprowadzasz "strażników danych" – publiczne właściwości. Każda próba ustawienia nowej wartości przechodzi przez blok sprawdzający (set), który weryfikuje sensowność danych (np. rok produkcji nie może być starszy niż pierwszy samochód Benza z 1885 roku). Dodatkowo wprowadzasz pole identyfikacyjne, które raz nadane, nigdy nie powinno ulec zmianie. Dzięki tym zabiegom, Twój system staje się odporny na błędy ludzkie i gwarantuje, że stan każdego obiektu jest zawsze poprawny i spójny.

Opis wykonania
  • Zmień modyfikatory pól marka, model i rokProdukcji na private.
  • Zastosuj konwencję nazewnictwa pól prywatnych zaczynającą się od podkreślenia (np. _marka).
  • Dodaj publiczną właściwość Marka z blokami get i set.
  • W bloku set dodaj walidację: jeśli nowa wartość (value) jest pusta lub składa się z samych spacji, przypisz "Nieznana".
  • Stwórz właściwość RokProdukcji z walidacją zakresu (od 1885 do bieżącego roku).
  • Wykorzystaj wyrażenie nameof() w komunikatach o błędach lub wewnątrz logiki walidacji.
  • Dodaj pole readonly string VIN, które może być ustawione tylko w miejscu deklaracji lub w konstruktorze (tu: w demonstracji bezpośrednio).
  • Zaimplementuj właściwość wyliczaną (tylko do odczytu) WiekSamochodu, która zwraca różnicę między obecnym rokiem a rokiem produkcji.
  • W metodzie Main utwórz obiekt i spróbuj przypisać mu błędne dane (np. rok 1500).
  • Zaobserwuj, jak właściwość koryguje dane lub uniemożliwia ich błędne ustawienie.
Kod źródłowy
Program.cs
using System;

namespace LaboratoriumOOP
{
    public class Samochod
    {
        // POLE PRYWATNE: Ukryte przed światem zewnętrznym.
        // Konwencja: _nazwa (camelCase z podkreśleniem).
        private string _marka;
        private int _rokProdukcji;
        
        // POLE TYLKO DO ODCZYTU: Można je ustawić tylko raz.
        public readonly string NumerSeryjny = "VIN-TEMP-2024";

        // WŁAŚCIWOŚĆ (Property): Publiczny interfejs dostępu do pola prywatnego.
        public string Marka
        {
            get { return _marka; }
            set 
            { 
                // Walidacja: value to niejawny parametr przekazywany do settera.
                if (string.IsNullOrWhiteSpace(value))
                {
                    Console.WriteLine($"OSTRZEŻENIE: Próba wpisania pustej marki dla {nameof(Marka)}.");
                    _marka = "Nieznana";
                }
                else
                {
                    _marka = value;
                }
            }
        }

        public string Model { get; set; } // Właściwość automatyczna (skrócony zapis)

        public int RokProdukcji
        {
            get { return _rokProdukcji; }
            set
            {
                // Zaawansowana walidacja
                int obecnyRok = DateTime.Now.Year;
                if (value < 1885 || value > obecnyRok)
                {
                    Console.WriteLine($"BŁĄD: Rok {value} jest poza zakresem (1885-{obecnyRok}). Ustawiam rok domyślny.");
                    _rokProdukcji = obecnyRok;
                }
                else
                {
                    _rokProdukcji = value;
                }
            }
        }

        // WŁAŚCIWOŚĆ WYLICZANA: Tylko do odczytu (brak bloku set).
        public int WiekSamochodu
        {
            get { return DateTime.Now.Year - _rokProdukcji; }
        }

        public void WyswietlInformacje()
        {
            Console.WriteLine($"[{NumerSeryjny}] {Marka} {Model}, Rok: {RokProdukcji} (Wiek: {WiekSamochodu} lat)");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("--- ZADANIE 02: Hermetyzacja i ochrona stanu ---\n");

            Samochod auto1 = new Samochod();
            
            // Korzystamy z właściwości zamiast pól
            auto1.Marka = "Audi";
            auto1.Model = "A4";
            auto1.RokProdukcji = 2018;

            Console.WriteLine("1. Prawidłowe dane:");
            auto1.WyswietlInformacje();

            Console.WriteLine("\n2. Próba wprowadzenia błędnych danych:");
            Samochod auto2 = new Samochod();
            auto2.Marka = "  "; // Próba wpisania pustego ciągu
            auto2.RokProdukcji = 1410; // Próba wpisania daty bitwy pod Grunwaldem
            
            Console.WriteLine("\nStan obiektu po walidacji:");
            auto2.WyswietlInformacje();

            // Próba zmiany pola readonly (odkomentowanie wywoła błąd kompilacji)
            // auto1.NumerSeryjny = "NOWY-VIN"; 

            Console.WriteLine("\nPodsumowanie: Dzięki hermetyzacji obiekt sam dba o swoją poprawność.");
            Console.WriteLine("Naciśnij dowolny klawisz, aby zakończyć...");
            Console.ReadKey();
        }
    }
}
                    
Wynik w konsoli
--- ZADANIE 02: Hermetyzacja i ochrona stanu --- 1. Prawidłowe dane: [VIN-TEMP-2024] Audi A4, Rok: 2018 (Wiek: 8 lat) 2. Próba wprowadzenia błędnych danych: OSTRZEŻENIE: Próba wpisania pustej marki dla Marka. BŁĄD: Rok 1410 jest poza zakresem (1885-2026). Ustawiam rok domyślny. Stan obiektu po walidacji: [VIN-TEMP-2024] Nieznana , Rok: 2026 (Wiek: 0 lat) Podsumowanie: Dzięki hermetyzacji obiekt sam dba o swoją poprawność. Naciśnij dowolny klawisz, aby zakończyć...