Odkryj 4 Filary Programowania Obiektowego w ASP.NET Core

4 Niesamowite Sztuczki do Opanowania Programowania Obiektowego w ASP.NET Core!

Orientacja obiektowa jest kluczowym paradygmatem dla twórców stron internetowych. W tym artykule poznasz 4 filary programowania obiektowego na przykładzie aplikacji ASP.NET Core.

Programowanie obiektowe ma zasadnicze znaczenie przy tworzeniu niezawodnych i skalowalnych aplikacji w ASP.NET Core. Dzięki zasadom programowania obiektowego programiści mogą tworzyć kod, który jest modułowy i wielokrotnego użytku, co prowadzi do większej spójności i mniejszego powiązania między komponentami. W rezultacie powstaje bardziej elastyczna i łatwa w utrzymaniu architektura.

Omówimy teraz cztery filary programowania obiektowego i pokaże, jak zaimplementować każdy z nich w aplikacji ASP.NET Core. Dzięki temu będziesz mógł tworzyć aplikacje obiektowe i lepiej zrozumieć każdą z tych koncepcji.

Co to jest programowanie obiektowe?

Programowanie obiektowe (OOP) to paradygmat, który powstał w latach 60. XX wieku, ale zyskał popularność głównie w latach 90. Stał się podstawą dla wielu języków programowania, takich jak C#, Java, C++, Python, Lua, PHP i inne. OOP wprowadza specjalny sposób pisania programów, w którym idee ze świata rzeczywistego są abstrahowane do oprogramowania za pomocą bloków zwanych „obiektami”.

Obiekt to abstrakcja rzeczywistego bytu lub zdarzenia, zawierająca atrybuty reprezentujące jego cechy lub właściwości oraz metody emulujące jego zachowanie.

Wyobraź sobie to jak elementy układanki puzzli. Każdy kawałek puzzli jest jak obiekt, który można łączyć z innymi, tworząc większe i bardziej złożone obrazy. Podobnie w OOP tworzysz obiekty, które mają swoje cechy (atrybuty) i działania, które mogą wykonać (metody).

Na przykład, jeśli piszesz program o filmach w bibliotece multimedialnej, możesz stworzyć obiekt o nazwie Film. Obiekt ten zawierałby informacje o filmie (atrybuty), takie jak tytuł, reżyser i rok produkcji, a także działania z nim związane (metody), takie jak wypożyczenie, zwrot i naliczanie opłat za zwłokę.

Głównym celem OOP jest podzielenie złożonego programu na mniejsze, łatwiejsze do zarządzania części. Dzięki temu programowanie jest bardziej zorganizowane i umożliwia ponowne wykorzystanie kodu, podobnie jak używanie różnych elementów puzzli do tworzenia wielu obrazów.

Patrząc na to z innej perspektywy, można powiedzieć, że programowanie obiektowe jest jak układanie wirtualnych puzzli na komputerze, podczas którego tworzysz obiekty o określonych cechach i działaniach, aby budować bardziej wydajne i elastyczne programy z elementami wielokrotnego użytku.

4 podstawowe zasady OOP

Te cztery podstawowe koncepcje są kluczowe dla programowania obiektowego, umożliwiając stworzenie bardziej uporządkowanego, łatwiejszego do ponownego wykorzystania i zrozumiałego kodu. Dzięki nim proces programowania staje się bardziej wydajny.

1. Abstrakcja

Abstrakcja polega na koncentracji tylko na kluczowych informacjach o danym przedmiocie, ignorując mniej istotne detale. W programowaniu abstrakcja umożliwia tworzenie klarownych modeli obiektów, ukrywając skomplikowane szczegóły i prezentując system w sposób abstrakcyjny.

2. Hermetyzacja

Hermetyzacja polega na umieszczaniu danych (atrybutów) i operacji (metod) wewnątrz klasy oraz kontrolowaniu, kto ma dostęp do nich. Ta praktyka pomaga ograniczyć konflikty między różnymi fragmentami kodu, co przekłada się na większą uporządkowanie i bezpieczeństwo programu.

3. Dziedziczenie

Dziedziczenie jest podobne do przekazywania cech z jednego elementu na drugi, analogicznie do sposobu, w jaki rodzice przekazują swoje cechy dzieciom. W programowaniu można tworzyć nowe klasy, bazując na istniejących klasach, które nazywane są klasami nadrzędnymi lub nadklasami. Nowa klasa dziedziczy atrybuty i metody klasy nadrzędnej, co umożliwia dodawanie nowych elementów lub modyfikowanie zachowania. Dzięki temu można efektywniej wykorzystywać kod i tworzyć hierarchię obiektów.

4. Polimorfizm

Polimorfizm to jak narzędzie, które może pełnić różne funkcje w zależności od kontekstu, podobnie jak uniwersalny narzędziowy adapter, który pasuje do różnych typów gniazdek. W programowaniu polimorfizm umożliwia różnym klasom współdzielenie tej samej nazwy metody, ale każda klasa implementuje tę metodę na swój unikalny sposób. To pozwala na jednolite traktowanie różnych obiektów, co sprawia, że kod staje się bardziej elastyczny i łatwiejszy do dostosowania.

OOP w ASP.NET Core

ASP.NET Core, opierając się na języku programowania C#, wykorzystuje paradygmat programowania obiektowego, który zapewnia wszystkie niezbędne funkcje do implementacji czterech zasad OOP.

Stworzmy teraz podstawową aplikację ASP.NET Core i wprowadzimy implementację każdej z tych zasad.

Warunki wstępne

Aby zbudować przykładową aplikację, będziesz potrzebować najnowszej wersji .NET zainstalowanej na swoim systemie. W tym poście używamy wersji 8 platformy .NET.

Konieczne będzie również posiadanie zintegrowanego środowiska programistycznego (IDE). W tym przypadku korzystamy z Visual Studio Code, które jest dostępne na systemach Windows, macOS i Linux.

Dostęp do kodu źródłowego aplikacji można uzyskać tutaj: Kod źródłowy.

Przykładową aplikacją będzie Web API do zarządzania filmami w wypożyczalni. Aby stworzyć bazę aplikacji, użyj poniższego polecenia w terminalu IDE:

dotnet new web -o MovieManager

Implementacja abstrakcji

Ponieważ nasza aplikacja dotyczy wypożyczalni, doskonałym przykładem abstrakcji będzie film – jest to rzeczywisty obiekt z powiązanymi atrybutami i działaniami, które będziemy replikować w kodzie.

Zdefiniujemy klasę o nazwie Movie jako główną jednostkę aplikacji. W języku C# klasa jest strukturą danych, która może zawierać elementy danych oraz funkcje, takie jak właściwości, metody i zdarzenia.

Zazwyczaj klasy, które reprezentują encje, są nazywane klasami “Modelu” i zgodnie z konwencją znajdują się w folderze o nazwie Models lub Entities.

Zatem w katalogu głównym projektu należy utworzyć nowy folder o nazwie Models i dodać w nim poniższą klasę:

namespace MovieManager.Models;

public class Movie
{
    public Guid Id { get; set; }
    public string? Title { get; set; }
    public string? Director { get; set; }
    public string? Genre { get; set; }
    public DateTime ReleaseDate { get; set; }
}

Należy zauważyć, że w powyższej utworzonej klasie Film abstrahujemy encję Film, która ma takie same właściwości jak rzeczywisty film, takie jak tytuł, reżyser, gatunek i rok produkcji.

Implementacja enkapsulacji

Implementacja enkapsulacji w OOP oznacza ukrywanie wewnętrznych szczegółów klasy i kontrolowany dostęp do jej elementów (atrybutów i metod) poprzez modyfikatory dostępu. W języku C# dostępne są różne modyfikatory dostępu, takie jak public, private, protected, internal oraz protected internal.

  • public: Członkowie publiczni są dostępni z dowolnego miejsca, zarówno w klasie, jak i poza nią.
  • private: Prywatne elementy członkowskie są dostępne tylko w klasie, w której zostały zadeklarowane.
  • protected: Elementy chronione są dostępne w klasie, która je deklaruje, oraz w klasach pochodnych (podklasach).
  • internal: Elementy wewnętrzne są dostępne w tym samym zestawie (plik .dll lub .exe).
  • protected internal: Ta kombinacja umożliwia dostęp w ramach tego samego zestawu, a także w klasach pochodnych, nawet jeśli znajdują się one w różnych zestawach.

Aby zastosować enkapsulację w aplikacji, utworzymy klasę, w której dodamy kilka metod. Wciąż w folderze Models utworzymy nową klasę o nazwie WypożyczalniaFilmów i umieścimy w niej poniższy kod:

using System.Text.Json;

namespace MovieManager.Models;

public class MovieRental
{
    private List<Movie> movies;
    private readonly string libraryFilePath;

    public MovieRental(string libraryFilePath)
    {
        this.libraryFilePath = libraryFilePath;
        LoadData();
    }

    public void AddMovie(Movie movie)
    {
        if (movie is ActionMovie actionMovie)
            movies.Add(actionMovie);
        else
            movies.Add(movie);
            
        SaveData();    
    }

    public void RemoveMovie(Guid movieId)
    {
        Movie movie = movies.FirstOrDefault(m => m.Id == movieId);
        if (movie != null)
        {
            movies.Remove(movie);
            SaveData();
        }
    }

    public IEnumerable<Movie> GetMovies()
    {
        return movies;
    }

    private void SaveData()
    {
        string jsonData = JsonSerializer.Serialize(movies);
        File.WriteAllText(libraryFilePath, jsonData);
    }

    private void LoadData()
    {
        if (File.Exists(libraryFilePath))
        {
            string jsonData = File.ReadAllText(libraryFilePath);
            movies = JsonSerializer.Deserialize<List<Movie>>(jsonData);
        }
        else
            movies = new List<Movie>();
    }
}

Zauważ, że w powyższym kodzie deklarujemy kilka modyfikatorów dostępu w zmiennych lokalnych (movies i filePathBook) oraz metodach (LoadData(), AddMovie() itp.).

Metody LoadData() i SaveData() są deklarowane jako prywatne, ponieważ nie ma potrzeby uzyskiwania dostępu do nich z zewnątrz. Zamiast tego dostęp do nich ma tylko klasa, która je implementuje. Natomiast metody AddMovie (), RemoveMovie() i GetMovies() są deklarowane jako publiczne, ponieważ muszą mieć do nich dostęp użytkownicy z zewnątrz.

W ten sposób realizujemy zasadę enkapsulacji poprzez modyfikatory dostępu.

Implementacja dziedziczenia

Koncepcja dziedziczenia w OOP odnosi się do faktu, że obiekty mogą dziedziczyć cechy i zachowania od innych obiektów.

Aby zaimplementować dziedziczenie w aplikacji, utwórzmy nową klasę modelową o nazwie „ActionMovie”, która będzie miała wszystkie cechy klasy „Movie” plus dwie wyjątkowe właściwości cyfrowego formatu książki. W tym przypadku klasa „ActionMovie” „odziedziczy” po klasie „Movie”.

W folderze Models utwórz nową klasę o nazwie „ActionMovie” i umieść w niej poniższy kod:

namespace MovieManager.Models;

public class ActionMovie : Movie
{
    public int ExplosionsCount { get; set; }
}

Aby zaimplementować dziedziczenie w C#, przed klasą, która otrzyma dziedziczenie (ActionMovie), stawiamy dwukropek. A po dwukropku umieszczamy klasę, która będzie dziedziczona (Movie), jak widać w powyższym kodzie. W ten sposób klasa ActionMovie ma te same właściwości co klasa Movie, takie jak identyfikator, tytuł, reżyser  itp.

Implementacja polimorfizmu

Polimorfizm pozwala na implementację metod przez różne klasy i na różne sposoby, co ułatwia ponowne użycie i organizację kodu.

W tym przykładzie mamy dwie główne jednostki, klasę „Movie” i klasę „ActionMovie”. Do tej pory stworzyliśmy metody tylko dla klasy „Movie”. Dzięki polimorfizmowi możemy ponownie wykorzystać metody klasy Movie dla klasy ActionMovie.

Aby to zrobić, dodaj następujący kod w klasie MovieRental:

public IEnumerable<ActionMovie> GetActionMovies()
{
   return movies.OfType<ActionMovie>();
}

W powyższym przykładzie metoda GetActionMovies() zwraca tylko te filmy akcji, które znajdują się w wypożyczalni, przy użyciu metody OfType<ActionMovie>(). Jest to możliwe dzięki polimorfizmowi, który pozwala traktować obiekty pochodne (takie jak ActionMovie) jak obiekty bazowe (takie jak Movie), o ile dziedziczenie jest prawidłowo skonfigurowane.

Uruchamianie aplikacji

Teraz, gdy rozumiemy już koncepcje czterech filarów OOP, uruchommy aplikację i zobaczmy w praktyce, jak zbudowany przez nas kod jest w pełni funkcjonalny.

Dlatego najpierw zainstaluj pakiety Swagger NuGet za pośrednictwem terminala, uruchamiając poniższe polecenia:

dotnet add package Microsoft.OpenApi
dotnet add package Swashbuckle.AspNetCore

Na koniec w pliku Program.cs zastąp istniejący kod poniższym kodem:

using MovieManager.Models;

var filePath = "movies.json";

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton(_ => new MovieRental(filePath));

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("/movies", (MovieRental library) =>
{
    var movies = library.GetMovies();
    return Results.Ok(movies);
});

app.MapGet("/movies/{id}", (Guid id, MovieRental library) =>
{
    var movie = library.GetMovies().FirstOrDefault(m => m.Id == id);
    if (movie == null)
        return Results.NotFound();
    return Results.Ok(movie);
});

app.MapPost("/movies", (Movie movie, MovieRental library) =>
{
    library.AddMovie(movie);
    return Results.Created($"/movies/{movie.Id}", movie);
});

app.MapDelete("/movies/{id}", (Guid id, MovieRental library) =>
{
    library.RemoveMovie(id);
    return Results.NoContent();
});

app.Run();

Teraz uruchom polecenie dotnet run w terminalu aplikacji. Następnie w przeglądarce uzyskaj dostęp: http://localhost:5207/swagger/index.html

W ten sposób możesz uruchomić funkcje CRUD i sprawdzić, czy działają idealnie, jak pokazano na poniższym obrazku:

Możesz użyć tego przykładu JSON, aby wykonać żądanie POST:

{
  "id": "c1d1a1b1-5678-1234-9abc-567890123456",
  "title": "The Great Gatsby",
  "director": "Baz Luhrmann",
  "genre": "Drama",
  "releaseDate": "2013-05-10T00:00:00"
}

Należy zauważyć, że w tym przykładowym scenariuszu używamy pliku JSON, który będzie przechowywać dane. W tym przypadku movies.OfType<ActionMovie>(); metoda nie będzie działać, ponieważ deserializator JSON nie jest w stanie odróżnić podklasy (ActionMovie) od klasy bazowej (Movie). Aby to zadziałało, musisz użyć ORM, takiego jak EF Core lub Dapper, i zaimplementować repozytorium, które uzyskuje dostęp do bazy danych.

Ponieważ celem tego wpisu jest zademonstrowanie użycia OOP, scenariusz dotyczący bazy danych nie zostanie omówiony, ale jeśli chcesz, możesz poczekać na kolejny wpis: Budowanie API CRUD za pomocą Dappera w celu zaimplementowania połączenia z prawdziwą bazą danych.

Wychodzenie poza podstawy

Oprócz czterech filarów OOP, ASP.NET Core ma kilka elementów niezbędnych do programowania obiektowego.

Interfaces – Interfejsy

Interfejsy definiują kontrakty, które klasy muszą implementować. Pozwalają zdefiniować wspólny zestaw metod, które różne klasy mogą implementować na różne, ale kompatybilne sposoby.

Przykład:

public interface IMovieService
{
    List<Movie> FindAllMovies();
}

Methods and Properties  – Metody i właściwości

W ASP.NET Core metody i właściwości służą do definiowania zachowania i charakterystyki klas. Metody wykonują akcje, a właściwości zapewniają dostęp do danych wewnętrznych.

Przykład:

//Method 
public void AddMovie(Movie movie)
{
    if (movie is ActionMovie actionMovie)
        movies.Add(actionMovie);
    else
        movies.Add(movie);
            
    SaveData();    
}


//Properties
public Guid Id { get; set; }
public string? Title { get; set; }
public string? Director { get; set; }
public string? Genre { get; set; }

Constructors and Destructors  – Konstruktory i destruktory

Konstruktory to unikalne metody używane do inicjowania obiektów podczas ich tworzenia. Natomiast destruktory służą do zwalniania zasobów związanych z obiektem, gdy jest on niszczony.

Przykład:

//Constructor
public class MovieLibrary
{
    private List<Movie> movies;
    private readonly string libraryFilePath;

    public MovieLibrary(string libraryFilePath)
    {
        this.libraryFilePath = libraryFilePath;
        LoadData();
    }
}

//Destructor
~ MovieLibrary ()
{
     _ = this.libraryFilePath;
}

Static Classes  – Klasy statyczne

Klasy statyczne zawierają statyczne elementy członkowskie, do których można uzyskać dostęp bez tworzenia instancji klasy. Jest to przydatne w celu zapewnienia współdzielonej funkcjonalności w całej aplikacji.

Przykład:

public static class Helper
{
    public static string ValidateMovieName(string movieName)
    {
        if (string.IsNullOrEmpty(movieName))
            return "Error: Movie name is mandatory";
        else
            return string.Empty;
    }
}

Namespace – Przestrzeń nazw

Przestrzenie nazw służą do organizowania i grupowania powiązanych klas w hierarchię. Pomagają uniknąć konfliktów nazw i modularyzują kod.

Przykład:

namespace MovieManager.Models;

Object-Oriented Design Patterns  Wzorce projektowe zorientowane obiektowo

ASP.NET Core często wykorzystuje wzorce projektowe zorientowane obiektowo, takie jak MVC (Model-View-Controller), aby oddzielić logikę biznesową, prezentację i interakcję z użytkownikiem.

Wniosek

Orientacja obiektowa to paradygmat umożliwiający programistom tworzenie solidnych i modułowych aplikacji, ułatwiających skalowalność i konserwację.

ASP.NET Core korzysta z języka programowania C#, który oprócz innych, takich jak interfejsy, konstruktory, metody i wzorce projektowe zorientowane obiektowo, implementuje cztery paradygmaty OOP (abstrakcja, enkapsulacja, dziedziczenie i polimorfizm).

W tym poście na blogu widzieliśmy, jak zaimplementować każdy z czterech paradygmatów OOP w aplikacji ASP.NET Core i jak te paradygmaty są ze sobą powiązane.

Tworząc aplikację lub funkcjonalność, rozważ wykorzystanie zasobów OOP.

1 comment

  1. Świetny artykuł! Bardzo dobrze wyjaśnione podstawy programowania obiektowego w ASP.NET Core. Szczególnie podoba mi się przykład z filmem – bardzo czytelny i łatwy do zrozumienia.

    Jestem początkującym programistą i ten artykuł zdecydowanie pomógł mi lepiej zrozumieć OOP. Z pewnością skorzystam z zaprezentowanych tu technik w moich projektach.

    Dziękuję za ten wpis!

Dodaj komentarz

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