Klasy i obiekty

Klasy i obiekty

Klasy i obiekty są kluczowymi koncepcjami w programowaniu obiektowym, a C# jest językiem programowania obiektowego, co oznacza, że opiera się na tych właśnie koncepcjach. Pozwalają na tworzenie struktur i organizację kodu w sposób bardziej zrozumiały, zarządzalny i elastyczny.

Oto szczegółowy opis klas i obiektów w C#:

1 Klasy

Definicja Klasy:

  • Klasa w języku C# to szablon lub plan, na podstawie którego tworzone są obiekty. Reprezentuje abstrakcję pewnego rodzaju jednostki w systemie.
  • Klasy są fundamentem programowania obiektowego, pozwalają na strukturyzację i organizację kodu.
  • Klasa definiuje cechy (pola), zachowanie (metody) oraz funkcjonalności obiektu.

Struktura Klasy:

  • Pola (Prywatne Dane): Pola przechowują dane charakteryzujące obiekt klasy. Są to zmienne, które należą do konkretnej instancji klasy.
  • Właściwości: Właściwości pozwalają na kontrolowany dostęp do pól klasy i umożliwiają walidację danych przed zapisem lub odczytem.
  • Metody (Funkcje): Metody to funkcje, które mogą operować na polach klasy i wykonywać różne operacje. Metody definiują zachowania obiektu.
  • Konstruktory: Konstruktor to specjalna metoda wywoływana podczas tworzenia nowej instancji klasy. Służy do inicjalizacji obiektu.
  • Indeksery: Indeksery pozwalają na dostęp do elementów obiektu za pomocą indeksu, podobnie jak w przypadku tablic. Wartość indeksu może być dowolnym typem, a nie tylko typem liczbowym.
  • Zdarzenia: Zdarzenia umożliwiają subskrypcję i obsługę zdarzeń wywoływanych w klasie.
  • Statyczność: Członkowie statyczni (metody, pola) należą do klasy, a nie do konkretnej instancji. Są dostępne bez konieczności tworzenia obiektu danej klasy.

przykład

public class Narzedzie
{
    public static int LiczbaNarzedzi;
}

Definicja Klasy przykład

public class Samochod
{
    // Członkowie klasy (pola, właściwości, metody)
}

Pola (Fields) i Właściwości (Properties)

public class Samochod
{
    public string Marka;        // Pole
    
    private int _rokProdukcji;  // Pole prywatne

    public int RokProdukcji     // Właściwość
    {
        get { return _rokProdukcji; }
        set { _rokProdukcji = value > 1900 ? value : 1900; }
    }
}

Metody

public class Samochod
{
    public void UruchomSilnik()
    {
        // Logika uruchamiania silnika
    }

    public int ObliczWiekSamochodu()
    {
        return DateTime.Now.Year - RokProdukcji;
    }
}

Konstruktory

public class Samochod
{
    public Samochod(string marka, int rokProdukcji)
    {
        Marka = marka;
        RokProdukcji = rokProdukcji;
    }
}

Indeksery

using System;

public class Biblioteka
{
    private string[] ksiazki = new string[10];

    // Indekser
    public string this[int indeks]
    {
        get
        {
            if (indeks >= 0 && indeks < ksiazki.Length)
            {
                return ksiazki[indeks];
            }
            else
            {
                return "Indeks poza zakresem";
            }
        }
        set
        {
            if (indeks >= 0 && indeks < ksiazki.Length)
            {
                ksiazki[indeks] = value;
            }
            else
            {
                Console.WriteLine("Indeks poza zakresem");
            }
        }
    }
}

class Program
{
    static void Main()
    {
        Biblioteka mojaBiblioteka = new Biblioteka();

        // Ustawienie wartości za pomocą indeksera
        mojaBiblioteka[0] = "Asp.Net Core";
        mojaBiblioteka[1] = "Csharp";
        mojaBiblioteka[2] = "Xml";

        // Pobranie wartości za pomocą indeksera
        Console.WriteLine(mojaBiblioteka[1]); // Wyświetli: Asp.Net Core
        Console.WriteLine(mojaBiblioteka[5]); // Wyświetli: Indeks poza zakresem
    }
}

Zdarzenia

using System;

// Klasa reprezentująca nadawcę zdarzenia
public class Nadawca
{
    // Definicja delegata zdarzenia
    public delegate void MojDelegat(string wiadomosc);

    // Zdarzenie oparte na delegacie
    public event MojDelegat MojEvent;

    // Metoda, która wywołuje zdarzenie
    public void WywolajZdarzenie(string wiadomosc)
    {
        Console.WriteLine("Nadawca: Wysyłam wiadomość - " + wiadomosc);

        // Sprawdzamy, czy zdarzenie ma zarejestrowane jakieś obszary nasłuchujące
        if (MojEvent != null)
        {
            // Wywołujemy zdarzenie, przekazując wiadomość
            MojEvent(wiadomosc);
        }
    }
}

// Klasa reprezentująca odbiorcę zdarzenia
public class Odbiorca
{
    // Metoda obsługująca zdarzenie
    public void ObslugaZdarzenia(string wiadomosc)
    {
        Console.WriteLine("Odbiorca: Otrzymałem wiadomość - " + wiadomosc);
    }
}

class Program
{
    static void Main()
    {
        // Tworzymy obiekty nadawcy i odbiorcy
        Nadawca mojNadawca = new Nadawca();
        Odbiorca mojOdbiorca = new Odbiorca();

        // Rejestrujemy metodę ObslugaZdarzenia jako obszar nasłuchujący zdarzenia w nadawcy
        mojNadawca.MojEvent += mojOdbiorca.ObslugaZdarzenia;

        // Wywołujemy zdarzenie
        mojNadawca.WywolajZdarzenie("Nowa wiadomość!");
 
    }
}

Oczekiwany wynik:

Nadawca: Wysyłam wiadomość - Nowa wiadomość!
Odbiorca: Otrzymałem wiadomość - Nowa wiadomość!

2 Obiekty

Definicja

  • Obiekt to konkretna instancja klasy.
  • Możemy utworzyć wiele obiektów na podstawie jednej klasy.
  • Każdy obiekt ma swoje unikalne atrybuty (wartości pól) i może wywoływać metody klasy.
  • Tworzenie obiektu polega na alokowaniu pamięci i inicjalizowaniu jego pól i właściwości zgodnie z definicją klasy.
  • Obiekty są niezależne i mogą mieć różne stany.

Tworzenie Obiektu:

  • new – obiekt jest tworzony za pomocą operatora new wraz z wywołaniem konstruktora klasy.
    Przykład: Samochod mojSamochod = new Samochod("Toyota", "Camry", 2022);
  • Referencje: Obiekty są przekazywane przez referencje. Oznacza to, że przypisanie jednego obiektu do innego nie tworzy nowego obiektu, ale kopiuje referencję do tego samego obiektu w pamięci.

Dostęp do Członków Klasy:

  • Pola i metody klasy mogą być dostępne z poziomu obiektu, a dostęp do nich odbywa się za pomocą operatora ..
  • Przykład: mojSamochod.PokazInformacje();
Osoba osoba1 = new Osoba("Jan", "Kowalski");
Osoba osoba2 = new Osoba("Anna", "Nowak");

osoba1.PrzedstawSie(); // "Jestem Jan Kowalski."
osoba2.PrzedstawSie(); // "Jestem Anna Nowak."

3 Filary Programowania obiektowego

Encapsulation (Enkapsulacja):

Enkapsulacja to zasada programowania obiektowego, która polega na ukrywaniu wewnętrznych detali implementacyjnych klasy i ujawnianiu tylko potrzebnych interfejsów.

Dostęp do pól i metod jest kontrolowany przez mechanizmy takie jak modyfikatory dostępu ( public, private, protected, internal, protected internal) które określają, które elementy klasy są widoczne dla innych części kodu. Co pomaga w zabezpieczaniu i izolowaniu wewnętrznych szczegółów implementacji.

Enkapsulacja pomaga w utrzymaniu spójności danych i umożliwia łatwiejsze utrzymanie kodu, ponieważ zmiany w implementacji mogą być ukrywane przed innymi częściami programu.

public class Samochod
{
    // private - dostęp tylko w obrębie klasy
    private int _rokProdukcji;

    // public - dostęp z dowolnego miejsca
    public int RokProdukcji
    {
        get { return _rokProdukcji; }
        set { _rokProdukcji = value > 1900 ? value : 1900; }
    }
}

Dziedziczenie

Dziedziczenie to mechanizm, który pozwala na tworzenie nowych klas na podstawie istniejących klas. Dziedziczenie umożliwia jednej klasie (klasie pochodnej) korzystanie z cech i zachowań innej klasy (klasy bazowej). Dziedziczenie pozwala na ponowne wykorzystanie kodu i tworzenie hierarchii klas, co ułatwia organizację kodu i wprowadzanie zmian w jednym miejscu.

public class Zwierze
{
    public void Oddychaj()
    {
        Console.WriteLine("Zwierze oddycha.");
    }
}

public class Ptak : Zwierze
{
    public void Lataj()
    {
        Console.WriteLine("Ptak lata.");
    }
}

Ptak dziedziczy metodę Oddychaj od klasy Zwierze.

Polimorfizm

Polimorfizm to koncepcja, która pozwala na traktowanie różnych obiektów w jednolity sposób. Możesz używać obiektów klas pochodnych jako obiektów klas bazowych.
Polimorfizm umożliwia jednemu interfejsowi obsługiwać różne typy obiektów lub jednej metodzie obsługiwać różne formy danych. W C#, polimorfizm może być osiągnięty poprzez przeciążanie metod, przesłanianie metod, interfejsy i klasy abstrakcyjne.

Metody wirtualne i abstrakcyjne umożliwiają nadpisywanie funkcji w klasach pochodnych.

public class Kształt
{
    public virtual void Narysuj()
    {
        Console.WriteLine("Rysuję kształt.");
    }
}

public class Kwadrat : Kształt
{
    public override void Narysuj()
    {
        Console.WriteLine("Rysuję kwadrat.");
    }
}

public class Kolo : Kształt
{
    public override void Narysuj()
    {
        Console.WriteLine("Rysuję koło.");
    }
}

Korzystając z polimorfizmu, możemy używać wspólnego interfejsu (Kształt) do obsługi różnych konkretnych implementacji (Kwadrat, Kolo).

Abstrakcja

Abstrakcja polega na ukrywaniu zbędnych detali implementacyjnych i skupianiu się na istotnych aspektach danego obiektu. Jest to proces tworzenia modelu, który koncentruje się na najważniejszych cechach i zachowaniach, ignorując mniej istotne szczegóły.

Klasy Abstrakcyjne:

Klasy abstrakcyjne są klasami, które nie mogą być instancjonowane bezpośrednio, ale mogą zawierać metody abstrakcyjne, czyli metody, które nie posiadają implementacji w klasie abstrakcyjnej. Klasy te służą jako szablony do dziedziczenia.

public abstract class Figura
{
    public abstract double PolePowierzchni(); // Metoda abstrakcyjna
}

Interfejsy:

Interfejsy to zbiorcze zestawy metod, które są implementowane przez różne klasy. W odróżnieniu od klas abstrakcyjnych, interfejsy nie zawierają żadnej implementacji i umożliwiają implementację wielu interfejsów w jednej klasie.

public interface IDzwieki
{
    void WydajDzwiek();
}

Właściwości Abstrakcyjne:

Właściwości abstrakcyjne pozwalają na definiowanie abstrakcyjnych pól w klasach abstrakcyjnych. Implementacja tych pól zostanie dostarczona przez klasę dziedziczącą.

public abstract class Pojazd
{
    public abstract string Marka { get; set; }
}

Abstrakcja pozwala na tworzenie bardziej ogólnych i elastycznych struktur w kodzie, co z kolei ułatwia rozbudowę i modyfikację systemu. Jest to kluczowa koncepcja w programowaniu obiektowym, która pomaga w modelowaniu rzeczywistych problemów i skupianiu się na istotnych aspektach systemu.

Filary programowania obiektowego stanowią podstawę dla wielu zaawansowanych koncepcji i technik w programowaniu. Wspierają one tworzenie kodu, który jest bardziej elastyczny, łatwiejszy do zrozumienia, utrzymania i rozbudowy. Programowanie obiektowe jest kluczowym paradygmatem programowania, który jest szeroko stosowany we współczesnym oprogramowaniu.

Oto kilka dodatkowych tematów, które warto zrozumieć:

Klasy i Metody Statyczne

Klasy statyczne zawierają tylko metody statyczne i nie mogą być instancjonowane. Metody statyczne są dostępne bez konieczności tworzenia obiektu danej klasy.

public static class Narzedzia
{
    public static void WykonajCos()
    {
        // Implementacja
    }
}

Metody Rozszerzeń

Metody rozszerzeń umożliwiają dodawanie nowych metod do istniejących typów bez modyfikowania ich definicji.

public static class RozszerzeniaString
{
    public static bool ZawieraSpacje(this string tekst)
    {
        return tekst.Contains(" ");
    }
}

Metody Generyczne

Metody generyczne pozwalają na tworzenie funkcji, które mogą operować na różnych typach danych, nie tracąc typów w trakcie kompilacji.

public T ZnajdzMax<T>(T a, T b) where T : IComparable<T>
{
    return a.CompareTo(b) > 0 ? a : b;
}

Typy Generyczne

Typy generyczne umożliwiają tworzenie klas, struktur, interfejsów itp., które mogą obsługiwać różne typy danych bez utraty bezpieczeństwa typów.

public class Kolejka<T>
{
    private List<T> elementy = new List<T>();

    public void Dodaj(T element)
    {
        elementy.Add(element);
    }
}

Właściwości Obliczane

Właściwości obliczane to właściwości, które nie przechowują wartości, ale obliczają ją dynamicznie w trakcie dostępu.

public class Kolo
{
    public double Promien { get; set; }

    public double PolePowierzchni
    {
        get { return Math.PI * Promien * Promien; }
    }
}

yield return

Umożliwia tworzenie iteratorów, zwracając kolejne elementy z sekwencji.

public IEnumerable<int> LiczbyParzyste()
{
    for (int i = 0; i < 10; i++)
    {
        if (i % 2 == 0)
            yield return i;
    }
}

unsafe

Umożliwia korzystanie z niebezpiecznych operacji w kontrolowanym środowisku.

unsafe
{
    int x = 10;
    int* ptr = &x;
}

Metody Wyrażeń (Expression-bodied Methods)

Skrócona składnia dla jednoliniowych metod.

public class Kalkulator
{
    public int Dodaj(int a, int b) => a + b;
}

Wyrażenia Lambda

Pozwalają na tworzenie krótkich anonimowych funkcji.

Func<int, int, int> dodaj = (x, y) => x + y;

Dekonstrukcja

Dostępna od C# 7.0, umożliwia rozpakowanie obiektu na składowe.

public class Osoba
{
    public string Imie { get; set; }
    public string Nazwisko { get; set; }

    public void Deconstruct(out string imie, out string nazwisko)
    {
        imie = Imie;
        nazwisko = Nazwisko;
    }
}

// Użycie
var osoba = new Osoba { Imie = "Jan", Nazwisko = "Kowalski" };
var (imię, nazwisko) = osoba;

Rekurencyjne Wyrażenia (Expression Trees)

Expression Trees pozwalają na reprezentację drzewa wyrażeń w postaci obiektowej. Jest to często używane w przypadku tworzenia dynamicznych zapytań LINQ.

Expression<Func<int, bool>> isEvenExpr = x => x % 2 == 0;

Atrybuty

Atrybuty pozwalają na dodawanie metadanych do kodu. Są używane do dostarczania informacji o kodzie dla kompilatora lub środowiska wykonawczego.

[Serializable]
public class MojaKlasa { }

Serializacja i Deserializacja

Serializacja to proces przekształcania obiektu w formę, która może być łatwo zapisana, na przykład do pliku. Deserializacja to proces odtwarzania obiektu z tej zapisanej formy.

var json = JsonSerializer.Serialize(mojObiekt);
var odtworzonyObiekt = JsonSerializer.Deserialize<MojTyp>(json);

Dispose Pattern (IDisposable)

Jeśli klasa używa zasobów nie zarządzanych (takich jak pliki, połączenia z bazą danych), może implementować interfejs IDisposable do zwalniania tych zasobów.

public class KlasaZasobow : IDisposable
{
    // Implementacja IDisposable
    public void Dispose()
    {
        // Zwolnienie zasobów
    }
}

Wstrzykiwanie Zależności

Wstrzykiwanie zależności to technika, w której obiekty otrzymują swoje zależności z zewnątrz, zamiast tworzyć je samodzielnie. To umożliwia bardziej elastyczne zarządzanie zależnościami.

public class Samochod
{
    private Silnik silnik;

    public Samochod(Silnik silnik)
    {
        this.silnik = silnik;
    }
}

Refleksja

Refleksja umożliwia analizowanie i manipulowanie metadanych programu w trakcie jego wykonywania. Można uzyskiwać informacje o typach, metodach, polach itp.

Type typ = typeof(MojaKlasa);
MethodInfo metoda = typ.GetMethod("MojaMetoda");

Asynchroniczność

W języku C# można używać słowa kluczowego async i await do tworzenia asynchronicznych metod, które pozwalają na efektywne zarządzanie operacjami oczekującymi na wyniki.

public async Task<int> PobierzDaneAsync()
{
    // Operacje asynchroniczne
    return await Task.FromResult(42);
}

Dynamiczne Typy

Dynamiczne typy w C# pozwalają na omijanie sprawdzania typów w czasie kompilacji, co umożliwia bardziej elastyczne operacje na danych.

dynamic dynamicznyObiekt = 42;
dynamic wynik = dynamicznyObiekt + "Tekst";

CQRS

CQRS to wzorzec projektowy, który podziela system na dwie części – jedną odpowiedzialną za obsługę zapytań (query), a drugą za obsługę poleceń (command).

// Przykład implementacji CQRS
public class ZapytanieHandler : IRequestHandler<Zapytanie, Wynik>
{
    public Task<Wynik> Handle(Zapytanie request, CancellationToken cancellationToken)
    {
        // Obsługa zapytania
    }
}

public class PolecenieHandler : IRequestHandler<Polecenie, Unit>
{
    public Task<Unit> Handle(Polecenie request, CancellationToken cancellationToken)
    {
        // Obsługa polecenia
    }
}

Wzorce Projektowe

Stosowanie różnych wzorców projektowych, takich jak Singleton, Fabryka, Strategia, Obserwator, itp., w celu rozwiązania konkretnych problemów projektowych.

Przykład zastosowania wzorca Strategia

using System;

// Interfejs definiujący operacje strategii
public interface IStrategia
{
    void WykonajAkcje();
}

// Implementacje różnych strategii
public class StrategiaA : IStrategia
{
    public void WykonajAkcje()
    {
        Console.WriteLine("Wykonuję akcję zgodnie z strategią A");
    }
}

public class StrategiaB : IStrategia
{
    public void WykonajAkcje()
    {
        Console.WriteLine("Wykonuję akcję zgodnie z strategią B");
    }
}

public class StrategiaC : IStrategia
{
    public void WykonajAkcje()
    {
        Console.WriteLine("Wykonuję akcję zgodnie z strategią C");
    }
}

// Klasa, która używa strategii
public class Klient
{
    private IStrategia strategia;

    // Konstruktor, umożliwiający ustawienie konkretnej strategii
    public Klient(IStrategia wybranaStrategia)
    {
        this.strategia = wybranaStrategia;
    }

    // Metoda wywołująca operację zgodnie z aktualnie wybraną strategią
    public void WykonajAkcjeZgodnieZeStrategia()
    {
        strategia.WykonajAkcje();
    }

    // Metoda umożliwiająca zmianę strategii w trakcie działania programu
    public void UstawStrategie(IStrategia nowaStrategia)
    {
        this.strategia = nowaStrategia;
    }
}

class Program
{
    static void Main()
    {
        // Tworzymy klienta i przekazujemy mu strategię A
        var klient = new Klient(new StrategiaA());

        // Klient wykonuje akcję zgodnie z aktualną strategią
        klient.WykonajAkcjeZgodnieZeStrategia();

        // Zmieniamy strategię klientowi na strategię B
        klient.UstawStrategie(new StrategiaB());

        // Klient ponownie wykonuje akcję, tym razem zgodnie z nową strategią
        klient.WykonajAkcjeZgodnieZeStrategia();
    }
}

W tym przykładzie wzorzec Strategia jest używany do odseparowania algorytmów od klienta. Klient może dynamicznie zmieniać strategię w trakcie działania programu, co umożliwia elastyczne dostosowywanie zachowań bez konieczności modyfikowania samego klienta. każda strategia implementuje wspólny interfejs IStrategia, co umożliwia zastępowanie jednej strategii drugą bez wprowadzania zmian w kodzie klienta.

To są tylko niektóre z zaawansowanych tematów związanych z klasami i obiektami w języku C#. Rozumienie tych zaawansowanych tematów pozwala na bardziej zaawansowane i elastyczne korzystanie z klas i obiektów w języku C#.

6 comments

  1. Całość jest dobrze zorganizowana i prezentuje kluczowe koncepcje programowania obiektowego w C#.
    Przykłady są zrozumiałe, a komentarze dodają wartości, wyjaśniając koncepcje i podając przykłady zastosowań.

  2. Klarowne przedstawienie filarów programowania obiektowego, a także wartościowa lista dodatkowych tematów do zrozumienia.
    Dodatkowe tematy, takie jak Klasy i Metody Statyczne, Metody Generyczne, czy Metody Rozszerzeń, są dobrze ujęte.

  3. Dobrze wytłumaczona koncepcja abstrakcji i różnice między klasami abstrakcyjnymi a interfejsami.
    Przykłady klas abstrakcyjnych i interfejsów są zrozumiałe.

  4. asne wyjaśnienie, co to jest obiekt i jak tworzyć instancje klas.
    Przekazanie informacji o referencjach w kontekście przypisywania obiektu do innego obiektu jest pomocne.

  5. Przykład z zdarzeniem jest świetny, pokazuje jak działa mechanizm subskrypcji i obsługi zdarzeń w C#.
    Dobrze, że zwrócono uwagę na sprawdzenie, czy zdarzenie ma zarejestrowane obszary nasłuchujące przed jego wywołaniem.

  6. Wyjaśnienie pól i właściwości jest zrozumiałe. Dobrze pokazane różnice między publicznymi polami a prywatnymi polami z właściwościami.
    Właściwość RokProdukcji zastosowująca walidację to dobry sposób na kontrolę danych.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *