Objectgeoriënteerd programmeren verkennen met klassen en objecten

In deze zelfstudie bouwt u een consoletoepassing en ziet u de eenvoudige objectgeoriënteerde functies die deel uitmaken van de C#-taal.

Vereisten

Uw toepassing maken

Maak met behulp van een terminalvenster een map met de naam Klassen. Daar bouwt u uw toepassing. Ga naar die map en typ dotnet new console het consolevenster. Met deze opdracht maakt u uw toepassing. Open Program.cs. Dit ziet er als volgt uit:

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

In deze zelfstudie gaat u nieuwe typen maken die een bankrekening vertegenwoordigen. Ontwikkelaars definiëren doorgaans elke klasse in een ander tekstbestand. Hierdoor wordt het eenvoudiger om te beheren naarmate een programma groter wordt. Maak een nieuw bestand met de naam BankAccount.cs in de map Klassen.

Dit bestand bevat de definitie van een bankrekening. Objectgeoriënteerd programmeren ordent code door typen te maken in de vorm van klassen. Deze klassen bevatten de code die een specifieke entiteit vertegenwoordigt. De BankAccount klasse vertegenwoordigt een bankrekening. De code implementeert specifieke bewerkingen via methoden en eigenschappen. In deze zelfstudie ondersteunt de bankrekening dit gedrag:

  1. Het heeft een getal van 10 cijfers dat de bankrekening uniek identificeert.
  2. Deze bevat een tekenreeks waarin de naam of namen van de eigenaren worden opgeslagen.
  3. Het saldo kan worden opgehaald.
  4. Het accepteert deposito's.
  5. Het accepteert intrekkingen.
  6. Het aanvankelijke saldo moet positief zijn.
  7. Intrekkingen kunnen geen negatief saldo opleveren.

Het type bankrekening definiëren

U kunt beginnen met het maken van de basisbeginselen van een klasse die dat gedrag definieert. Maak een nieuw bestand met de opdracht File:New . Noem het BankAccount.cs. Voeg de volgende code toe aan uw BankAccount.cs-bestand :

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)
    {
    }
}

Voordat we verdergaan, gaan we eens kijken wat u hebt gebouwd. De namespace declaratie biedt een manier om uw code logisch te ordenen. Deze zelfstudie is relatief klein, dus u plaatst alle code in één naamruimte.

public class BankAccount definieert de klasse of het type dat u maakt. Alles binnen de { en } die volgt op de klassedeclaratie definieert de status en het gedrag van de klasse. Er zijn vijf leden van de BankAccount klas. De eerste drie zijn eigenschappen. Eigenschappen zijn gegevenselementen en kunnen code bevatten waarmee validatie of andere regels worden afgedwongen. De laatste twee zijn methoden. Methoden zijn codeblokken die één functie uitvoeren. Als u de namen van elk van de leden leest, moet u of een andere ontwikkelaar voldoende informatie geven om te begrijpen wat de klasse doet.

Een nieuw account openen

De eerste functie die moet worden geïmplementeerd, is het openen van een bankrekening. Wanneer een klant een account opent, moet deze een eerste saldo en informatie over de eigenaar of eigenaren van dat account opgeven.

Het maken van een nieuw object van het BankAccount type betekent het definiëren van een constructor die deze waarden toewijst. Een constructor is een lid met dezelfde naam als de klasse. Het wordt gebruikt om objecten van dat klassetype te initialiseren. Voeg de volgende constructor toe aan het BankAccount type. Plaats de volgende code boven de declaratie van MakeDeposit:

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

De voorgaande code identificeert de eigenschappen van het object dat wordt samengesteld door de this kwalificatie in te delen. Die kwalificatie is meestal optioneel en weggelaten. U kunt ook het volgende hebben geschreven:

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

De this kwalificatie is alleen vereist wanneer een lokale variabele of parameter dezelfde naam heeft als dat veld of die eigenschap. De this kwalificatie wordt in de rest van dit artikel weggelaten, tenzij dit nodig is.

Constructors worden aangeroepen wanneer u een object maakt met behulp van new. Vervang de regel Console.WriteLine("Hello World!"); in Program.cs door de volgende code (vervang <name> door uw naam):

using Classes;

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

Laten we eens uitvoeren wat u tot nu toe hebt gebouwd. Als u Visual Studio gebruikt, selecteert u Start zonder foutopsporing in het menu Foutopsporing . Als u een opdrachtregel gebruikt, typt dotnet run u de map waarin u het project hebt gemaakt.

Hebt u gemerkt dat het accountnummer leeg is? Het is tijd om dat op te lossen. Het accountnummer moet worden toegewezen wanneer het object is samengesteld. Maar het mag niet de verantwoordelijkheid van de beller zijn om deze te maken. De BankAccount klassecode moet weten hoe nieuwe accountnummers moeten worden toegewezen. Een eenvoudige manier is om te beginnen met een getal van 10 cijfers. Verhoog het wanneer elk nieuw account wordt gemaakt. Sla ten slotte het huidige rekeningnummer op wanneer een object wordt samengesteld.

Voeg een liddeclaratie toe aan de BankAccount klasse. Plaats de volgende regel code na de accolade { openen aan het begin van de BankAccount klasse:

private static int s_accountNumberSeed = 1234567890;

Het accountNumberSeed is een gegevenslid. privateDit betekent dat het alleen toegankelijk is via code in de BankAccount klasse. Het is een manier om de openbare verantwoordelijkheden (zoals een rekeningnummer) te scheiden van de privé-implementatie (hoe rekeningnummers worden gegenereerd). Het is ook static, wat betekent dat het wordt gedeeld door alle BankAccount objecten. De waarde van een niet-statische variabele is uniek voor elk exemplaar van het BankAccount object. Het accountNumberSeed is een private static veld en heeft dus het s_ voorvoegsel volgens C#-naamconventies. Het s veld dat aangeeft static en _ aangeeft private . Voeg de volgende twee regels toe aan de constructor om het rekeningnummer toe te wijzen. Plaats ze na de regel met de tekst this.Balance = initialBalance:

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

Typ dotnet run om de resultaten weer te geven.

Stortingen en intrekkingen maken

Uw bankrekeningklasse moet deposito's en intrekkingen accepteren om correct te kunnen werken. Laten we stortingen en intrekkingen implementeren door een logboek te maken van elke transactie voor het account. Het bijhouden van elke transactie heeft enkele voordelen ten opzichte van het bijwerken van het saldo voor elke transactie. De geschiedenis kan worden gebruikt om alle transacties te controleren en dagelijkse saldi te beheren. Het berekenen van het saldo uit de geschiedenis van alle transacties wanneer dat nodig is, zorgt ervoor dat eventuele fouten in één transactie die zijn opgelost, correct worden weergegeven in het saldo voor de volgende berekening.

Laten we beginnen met het maken van een nieuw type om een transactie weer te geven. De transactie is een eenvoudig type dat geen verantwoordelijkheden heeft. Er zijn enkele eigenschappen nodig. Maak een nieuw bestand met de naam Transaction.cs. Voeg de volgende code toe aan de klasse:

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;
    }
}

Nu gaan we een List<T> van Transaction de objecten aan de BankAccount klasse toevoegen. Voeg de volgende declaratie toe na de constructor in uw BankAccount.cs-bestand :

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

Laten we nu de Balance. Het huidige saldo kan worden gevonden door de waarden van alle transacties op te tellen. Zoals de code momenteel is, kunt u alleen het eerste saldo van het account ophalen, dus moet u de Balance eigenschap bijwerken. Vervang de regel public decimal Balance { get; } in BankAccount.cs door de volgende code:

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

        return balance;
    }
}

In dit voorbeeld ziet u een belangrijk aspect van eigenschappen. U rekent nu het saldo op wanneer een andere programmeur om de waarde vraagt. Uw berekening bevat alle transacties en levert de som als het huidige saldo.

Implementeer vervolgens de MakeDeposit en MakeWithdrawal methoden. Met deze methoden worden de laatste twee regels afgedwongen: het oorspronkelijke saldo moet positief zijn en elke intrekking mag geen negatief saldo creëren.

Deze regels introduceren het concept van uitzonderingen. De standaardmethode waarmee wordt aangegeven dat een methode het werk niet kan voltooien, is door een uitzondering te genereren. Het type uitzondering en het bijbehorende bericht beschrijven de fout. Hier genereert de MakeDeposit methode een uitzondering als het bedrag van de storting niet groter is dan 0. De MakeWithdrawal methode genereert een uitzondering als het opnamebedrag niet groter is dan 0 of als het toepassen van de opname resulteert in een negatief saldo. Voeg de volgende code toe na de declaratie van de _allTransactions lijst:

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);
}

De throw instructiegenereert een uitzondering. Uitvoering van het huidige blok eindigt en besturingsoverdrachten naar het eerste overeenkomende catch blok dat in de aanroepstack is gevonden. U voegt later een catch blok toe om deze code te testen.

De constructor moet één wijziging krijgen, zodat er een initiële transactie wordt toegevoegd in plaats van het saldo rechtstreeks bij te werken. Omdat u de methode al hebt geschreven, roept u deze MakeDeposit aan vanuit uw constructor. De voltooide constructor moet er als volgt uitzien:

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

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

DateTime.Now is een eigenschap die de huidige datum en tijd retourneert. Test deze code door enkele stortingen en intrekkingen toe te voegen aan uw Main methode, met behulp van de code waarmee een nieuwe BankAccountwordt gemaakt:

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

Test vervolgens of u foutvoorwaarden onderscheppen door een account met een negatief saldo te maken. Voeg de volgende code toe na de voorgaande code die u zojuist hebt toegevoegd:

// 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;
}

U gebruikt de try-catch instructie om een codeblok te markeren dat uitzonderingen kan genereren en om die fouten te ondervangen die u verwacht. U kunt dezelfde techniek gebruiken om de code te testen die een uitzondering genereert voor een negatief saldo. Voeg de volgende code toe vóór de declaratie van invalidAccount in uw Main methode:

// 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());
}

Sla het bestand op en typ dotnet run het om het te proberen.

Uitdaging: alle transacties registreren

Als u deze zelfstudie wilt voltooien, kunt u de GetAccountHistory methode schrijven waarmee een string voor de transactiegeschiedenis wordt gemaakt. Voeg deze methode toe aan het BankAccount type:

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();
}

De geschiedenis gebruikt de StringBuilder klasse om een tekenreeks op te maken die één regel voor elke transactie bevat. U hebt de tekenreeksopmaakcode eerder in deze zelfstudies gezien. Eén nieuw teken is \t. Hiermee wordt een tabblad ingevoegd om de uitvoer op te maken.

Voeg deze regel toe om deze te testen in Program.cs:

Console.WriteLine(account.GetAccountHistory());

Voer uw programma uit om de resultaten te bekijken.

Volgende stappen

Als u vastloopt, kunt u de bron voor deze zelfstudie zien in onze GitHub-opslagplaats.

U kunt doorgaan met de zelfstudie objectgeoriënteerd programmeren .

Meer informatie over deze concepten vindt u in deze artikelen: