Eksplorowanie programowania obiektowego za pomocą klas i obiektów

W tym samouczku utworzysz aplikację konsolową i zobaczysz podstawowe funkcje zorientowane na obiekt, które są częścią języka C#.

Wymagania wstępne

  • Zalecamy program Visual Studio dla systemu Windows lub Mac. Bezpłatną wersję można pobrać ze strony pobierania programu Visual Studio. Program Visual Studio zawiera zestaw .NET SDK.
  • Możesz również użyć edytora programu Visual Studio Code . Musisz zainstalować oddzielnie najnowszy zestaw .NET SDK .
  • Jeśli wolisz inny edytor, musisz zainstalować najnowszy zestaw .NET SDK.

Tworzenie aplikacji

Za pomocą okna terminalu utwórz katalog o nazwie Classes. Utworzysz tam aplikację. Przejdź do tego katalogu i wpisz dotnet new console polecenie w oknie konsoli. To polecenie tworzy aplikację. Otwórz plik Program.cs. Powinien on wyglądać następująco:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

W tym samouczku utworzysz nowe typy reprezentujące konto bankowe. Zazwyczaj deweloperzy definiują każdą klasę w innym pliku tekstowym. Ułatwia to zarządzanie w miarę wzrostu rozmiaru programu. Utwórz nowy plik o nazwie BankAccount.cs w katalogu Classes .

Ten plik będzie zawierać definicję konta bankowego. Programowanie obiektowe organizuje kod, tworząc typy w postaci klas. Te klasy zawierają kod reprezentujący określoną jednostkę. Klasa BankAccount reprezentuje konto bankowe. Kod implementuje określone operacje za pomocą metod i właściwości. W tym samouczku konto bankowe obsługuje następujące zachowanie:

  1. Ma 10-cyfrowy numer, który jednoznacznie identyfikuje konto bankowe.
  2. Zawiera on ciąg, który przechowuje nazwę lub nazwy właścicieli.
  3. Saldo można pobrać.
  4. Akceptuje depozyty.
  5. Akceptuje wypłaty.
  6. Początkowe saldo musi być dodatnie.
  7. Wypłaty nie mogą skutkować ujemnym saldem.

Definiowanie typu konta bankowego

Możesz zacząć od utworzenia podstaw klasy, która definiuje to zachowanie. Utwórz nowy plik przy użyciu polecenia File:New . Nadaj mu nazwę BankAccount.cs. Dodaj następujący kod do pliku BankAccount.cs :

namespace Classes;

public class BankAccount
{
    public string Number { get; }
    public string Owner { get; set; }
    public decimal Balance { get; }

    public void MakeDeposit(decimal amount, DateTime date, string note)
    {
    }

    public void MakeWithdrawal(decimal amount, DateTime date, string note)
    {
    }
}

Przed rozpoczęciem przyjrzyjmy się temu, co zostało utworzone. Deklaracja namespace umożliwia logiczne organizowanie kodu. Ten samouczek jest stosunkowo mały, więc cały kod zostanie umieszczony w jednej przestrzeni nazw.

public class BankAccount definiuje klasę lub typ, który tworzysz. Wszystko wewnątrz klasy { i } , które następuje zgodnie z deklaracją klasy definiuje stan i zachowanie klasy. Istnieje pięć członkówBankAccount klasy. Pierwsze trzy są właściwościami. Właściwości to elementy danych i mogą mieć kod wymuszający walidację lub inne reguły. Ostatnie dwa są metodami. Metody to bloki kodu, które wykonują jedną funkcję. Odczytywanie nazw poszczególnych elementów członkowskich powinno dostarczyć użytkownikowi lub innemu deweloperowi wystarczające informacje, aby zrozumieć, co robi klasa.

Otwieranie nowego konta

Pierwszą funkcją implementacji jest otwarcie konta bankowego. Gdy klient otworzy konto, musi podać początkowe saldo oraz informacje o właścicielu lub właścicielach tego konta.

Utworzenie nowego obiektu BankAccount typu oznacza zdefiniowanie konstruktora, który przypisuje te wartości. Konstruktor jest elementem członkowskim o takiej samej nazwie jak klasa. Służy do inicjowania obiektów tego typu klasy. Dodaj następujący konstruktor do BankAccount typu . Umieść następujący kod powyżej deklaracji MakeDeposit:

public BankAccount(string name, decimal initialBalance)
{
    this.Owner = name;
    this.Balance = initialBalance;
}

Powyższy kod identyfikuje właściwości obiektu konstruowanego przez dołączenie kwalifikatora this . Ten kwalifikator jest zwykle opcjonalny i pomijany. Można również napisać:

public BankAccount(string name, decimal initialBalance)
{
    Owner = name;
    Balance = initialBalance;
}

this Kwalifikator jest wymagany tylko wtedy, gdy zmienna lokalna lub parametr ma taką samą nazwę jak to pole lub właściwość. this Kwalifikator zostanie pominięty w pozostałej części tego artykułu, chyba że jest to konieczne.

Konstruktory są wywoływane podczas tworzenia obiektu przy użyciu polecenia new. Zastąp wiersz Console.WriteLine("Hello World!"); w Program.cs następującym kodem (zastąp <name> ciąg nazwą):

using Classes;

var account = new BankAccount("<name>", 1000);
Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance.");

Uruchommy to, co zostało utworzone do tej pory. Jeśli używasz programu Visual Studio, wybierz pozycję Rozpocznij bez debugowania z menu Debugowanie . Jeśli używasz wiersza polecenia, wpisz dotnet run katalog, w którym utworzono projekt.

Czy zauważyłeś, że numer konta jest pusty? Nadszedł czas, aby to naprawić. Numer konta należy przypisać podczas konstruowania obiektu. Ale nie powinno to być obowiązkiem rozmówców, aby go stworzyć. Kod BankAccount klasy powinien wiedzieć, jak przypisać nowe numery kont. Prostym sposobem jest rozpoczęcie od 10-cyfrowej liczby. Zwiększ go po utworzeniu każdego nowego konta. Na koniec zapisz numer bieżącego konta podczas konstruowania obiektu.

Dodaj deklarację składową do BankAccount klasy. Umieść następujący wiersz kodu po nawiasie klamrowym { otwierającym na początku BankAccount klasy:

private static int s_accountNumberSeed = 1234567890;

Element accountNumberSeed jest elementem członkowskim danych. privateJest to , co oznacza, że dostęp do niego można uzyskać tylko za pomocą kodu wewnątrz BankAccount klasy. Jest to sposób oddzielenia obowiązków publicznych (takich jak posiadanie numeru konta) od implementacji prywatnej (sposób generowania numerów kont). Jest to również staticelement , co oznacza, że jest współużytkowany BankAccount przez wszystkie obiekty. Wartość zmiennej niestatycznej jest unikatowa dla każdego wystąpienia BankAccount obiektu. Jest accountNumberSeed to pole, w związku z private static czym ma s_ prefiks zgodnie z konwencjami nazewnictwa języka C#. Oznaczanie s i _ oznaczanie staticprivate pola. Dodaj następujące dwa wiersze do konstruktora, aby przypisać numer konta. Umieść je po wierszu z napisem this.Balance = initialBalance:

Number = s_accountNumberSeed.ToString();
s_accountNumberSeed++;

Wpisz dotnet run , aby wyświetlić wyniki.

Tworzenie depozytów i wypłat

Klasa konta bankowego musi zaakceptować depozyty i wypłaty, aby działały prawidłowo. Zaimplementujmy depozyty i wypłaty, tworząc dziennik każdej transakcji dla konta. Śledzenie każdej transakcji ma kilka zalet w stosunku do prostego aktualizowania salda dla każdej transakcji. Historia może służyć do inspekcji wszystkich transakcji i zarządzania dziennymi saldami. Obliczenie salda z historii wszystkich transakcji w razie potrzeby gwarantuje, że wszelkie błędy w jednej transakcji, które zostały naprawione, zostaną poprawnie odzwierciedlone w saldu na następnej obliczeniach.

Zacznijmy od utworzenia nowego typu reprezentującego transakcję. Transakcja jest prostym typem, który nie ma żadnych obowiązków. Potrzebuje kilku właściwości. Utwórz nowy plik o nazwie Transaction.cs. Dodaj do niej następujący kod:

namespace Classes;

public class Transaction
{
    public decimal Amount { get; }
    public DateTime Date { get; }
    public string Notes { get; }

    public Transaction(decimal amount, DateTime date, string note)
    {
        Amount = amount;
        Date = date;
        Notes = note;
    }
}

Teraz dodajmy List<T> obiekt BankAccount do Transaction klasy . Dodaj następującą deklarację po konstruktorze w pliku BankAccount.cs :

private List<Transaction> _allTransactions = new List<Transaction>();

Teraz poprawnie obliczmy element Balance. Bieżące saldo można znaleźć, sumując wartości wszystkich transakcji. Ponieważ kod jest obecnie, możesz uzyskać tylko początkowe saldo konta, więc trzeba będzie zaktualizować Balance właściwość. Zastąp wiersz public decimal Balance { get; } w BankAccount.cs następującym kodem:

public decimal Balance
{
    get
    {
        decimal balance = 0;
        foreach (var item in _allTransactions)
        {
            balance += item.Amount;
        }

        return balance;
    }
}

W tym przykładzie przedstawiono ważny aspekt właściwości. Teraz obliczysz saldo, gdy inny programista pyta o wartość. Obliczenia wyliczają wszystkie transakcje i udostępniają sumę jako bieżące saldo.

Następnie zaimplementuj MakeDeposit metody i MakeWithdrawal . Te metody wymuszają dwie ostatnie reguły: początkowe saldo musi być dodatnie, a każde wycofanie nie może utworzyć ujemnego salda.

Te reguły wprowadzają pojęcie wyjątków. Standardowym sposobem wskazywania, że metoda nie może pomyślnie ukończyć swojej pracy, jest zgłoszenie wyjątku. Typ wyjątku i skojarzony z nim komunikat opisują błąd. W tym miejscu metoda zgłasza wyjątek, MakeDeposit jeśli kwota depozytu nie jest większa niż 0. Metoda MakeWithdrawal zgłasza wyjątek, jeśli kwota wypłaty nie jest większa niż 0, lub jeśli zastosowanie wypłaty spowoduje ujemne saldo. Dodaj następujący kod po deklaracji _allTransactions listy:

public void MakeDeposit(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
    }
    var deposit = new Transaction(amount, date, note);
    _allTransactions.Add(deposit);
}

public void MakeWithdrawal(decimal amount, DateTime date, string note)
{
    if (amount <= 0)
    {
        throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive");
    }
    if (Balance - amount < 0)
    {
        throw new InvalidOperationException("Not sufficient funds for this withdrawal");
    }
    var withdrawal = new Transaction(-amount, date, note);
    _allTransactions.Add(withdrawal);
}

Instrukcja throw zgłasza wyjątek. Wykonanie bieżącego bloku kończy się i przeniesienie kontrolek do pierwszego pasującego catch bloku znalezionego w stosie wywołań. Dodasz catch blok, aby przetestować ten kod nieco później.

Konstruktor powinien uzyskać jedną zmianę, aby dodawał początkową transakcję, zamiast aktualizować saldo bezpośrednio. Ponieważ już napisałeś metodę MakeDeposit , wywołaj ją z konstruktora. Gotowy konstruktor powinien wyglądać następująco:

public BankAccount(string name, decimal initialBalance)
{
    Number = s_accountNumberSeed.ToString();
    s_accountNumberSeed++;

    Owner = name;
    MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}

DateTime.Now jest właściwością zwracającą bieżącą datę i godzinę. Przetestuj ten kod, dodając kilka depozytów i wypłat w metodzie Main , postępując zgodnie z kodem, który tworzy nowy BankAccountkod :

account.MakeWithdrawal(500, DateTime.Now, "Rent payment");
Console.WriteLine(account.Balance);
account.MakeDeposit(100, DateTime.Now, "Friend paid me back");
Console.WriteLine(account.Balance);

Następnie przetestuj, czy przechwytujesz warunki błędu, próbując utworzyć konto z ujemnym saldem. Dodaj następujący kod po właśnie dodanym kodzie:

// Test that the initial balances must be positive.
BankAccount invalidAccount;
try
{
    invalidAccount = new BankAccount("invalid", -55);
}
catch (ArgumentOutOfRangeException e)
{
    Console.WriteLine("Exception caught creating account with negative balance");
    Console.WriteLine(e.ToString());
    return;
}

Instrukcja służy try-catch do oznaczania bloku kodu, który może zgłaszać wyjątki i przechwytywać oczekiwane błędy. Możesz użyć tej samej techniki, aby przetestować kod, który zgłasza wyjątek dla ujemnego salda. Dodaj następujący kod przed deklaracją invalidAccount w metodzie Main :

// Test for a negative balance.
try
{
    account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw");
}
catch (InvalidOperationException e)
{
    Console.WriteLine("Exception caught trying to overdraw");
    Console.WriteLine(e.ToString());
}

Zapisz plik i wpisz dotnet run , aby go wypróbować.

Wyzwanie — rejestrowanie wszystkich transakcji

Aby ukończyć ten samouczek, możesz napisać GetAccountHistory metodę, która tworzy string obiekt dla historii transakcji. Dodaj tę metodę do BankAccount typu:

public string GetAccountHistory()
{
    var report = new System.Text.StringBuilder();

    decimal balance = 0;
    report.AppendLine("Date\t\tAmount\tBalance\tNote");
    foreach (var item in _allTransactions)
    {
        balance += item.Amount;
        report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}");
    }

    return report.ToString();
}

Historia używa StringBuilder klasy do formatowania ciągu zawierającego jeden wiersz dla każdej transakcji. Kod formatowania ciągów został już wcześniej wyświetlony w tych samouczkach. Jeden nowy znak to \t. Powoduje to wstawienie karty w celu sformatowania danych wyjściowych.

Dodaj ten wiersz, aby przetestować go w Program.cs:

Console.WriteLine(account.GetAccountHistory());

Uruchom program, aby wyświetlić wyniki.

Następne kroki

Jeśli utkniesz, możesz zobaczyć źródło tego samouczka w naszym repozytorium GitHub.

Możesz kontynuować pracę z samouczkiem dotyczącym programowania obiektowego .

Więcej informacji na temat tych pojęć można uzyskać w następujących artykułach: