Esplorare la programmazione orientata agli oggetti con classi e oggetti

Durante l’esercitazione verrà illustrato come creare un'applicazione console e verranno presentate le funzionalità orientate a oggetti di base che fanno parte del linguaggio C#.

Prerequisiti

  • È consigliabile usare Visual Studio per Windows. È possibile scaricare una versione gratuita dalla pagina di download di Visual Studio. Visual Studio include la SDK .NET.
  • È possibile utilizzare anche l'editor di Visual Studio Code con il DevKit C#. Sarà necessario installare separatamente la versione più recente di SDK .NET.
  • Se si preferisce un editor diverso, è necessario installare la versione più recente di SDK .NET.

Creare l'applicazione

Usare una finestra del terminale per creare una directory denominata classi. L'applicazione verrà creata in questa posizione. Passare alla directory e digitare dotnet new console nella finestra della console. Questo comando crea l'applicazione. Aprire Program.cs. La cartella dovrebbe avere un aspetto simile a questo:

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

In questa esercitazione verranno creati nuovi tipi che rappresentano un conto bancario. In genere, gli sviluppatori definiscono ogni classe in un file di testo diverso. In questo modo è più semplice gestire l'aumento di dimensioni di un programma. Creare un nuovo file denominato BankAccount.cs nella directory classi.

Questo file conterrà la definizione di un conto bancario. Nella programmazione orientata a oggetti il codice viene organizzato tramite la creazione di tipi sotto forma di classi. Queste classi contengono il codice che rappresenta un'entità specifica. La classe BankAccount rappresenta un conto bancario. Il codice implementa operazioni specifiche tramite metodi e proprietà. In questa esercitazione il conto bancario supporta questo comportamento:

  1. Esiste un numero di 10 cifre che identifica in modo univoco il conto.
  2. Esiste una stringa in cui vengono archiviati i nomi dei titolari.
  3. È possibile recuperare il saldo.
  4. Il conto accetta versamenti.
  5. Il conto accetta prelievi.
  6. Il saldo iniziale deve essere positivo.
  7. I prelievi non possono risultare in un saldo negativo.

Definire il tipo di conto bancario

Per iniziare, è possibile creare gli elementi di base di una classe che definisce questo comportamento. Creare un nuovo file usando il comando File:New. Denominarlo BankAccount.cs. Aggiungere il codice seguente al file 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)
    {
    }
}

Prima di procedere, è opportuno esaminare il codice finora creato. La dichiarazione namespace offre un modo per organizzare logicamente il codice. Poiché questa esercitazione è relativamente breve, si inserirà il codice in uno spazio dei nomi.

public class BankAccount definisce la classe, o tipo, che si sta creando. Tutti gli elementi racchiusi tra le parentesi { e } che seguono la dichiarazione della classe definiscono lo stato e il comportamento della classe. Esistono cinque membri della classe BankAccount. I primi tre sono proprietà. Le proprietà sono elementi di dati e possono contenere codice per l'applicazione della convalida o di altre regole. Gli ultimi due sono metodi. I metodi sono blocchi di codice che eseguono una singola funzione. La lettura dei nomi di ogni membro dovrebbe fornire informazioni sufficienti per consentire all'utente o a un altro sviluppatore di capire gli scopi della classe.

Aprire un nuovo conto

La prima funzionalità da implementare è l'apertura di un conto bancario. Quando un cliente apre un conto, deve specificare il saldo iniziale e informazioni sul titolare o i titolari del conto.

La creazione di un nuovo oggetto di tipo BankAccount implica la definizione di un costruttore che assegna questi valori. Un costruttore è un membro con lo stesso nome della classe e viene usato per inizializzare oggetti di quel tipo di classe. Aggiungere il costruttore seguente al tipo BankAccount. Inserire il codice seguente sopra la dichiarazione di MakeDeposit:

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

Il codice precedente identifica le proprietà dell'oggetto costruito includendo il qualificatore this. Questo qualificatore è in genere facoltativo e omesso. È inoltre possibile scrivere:

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

Il qualificatore this è obbligatorio solo quando una variabile o un parametro locale ha lo stesso nome di tale campo o proprietà. Il qualificatore this viene omesso nel resto di questo articolo, a meno che non sia necessario.

I costruttori vengono chiamati quando si crea un oggetto con new. Sostituire la riga Console.WriteLine("Hello World!"); in program.cs con il codice seguente (sostituire <name> con il proprio nome):

using Classes;

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

Eseguiamo quello che è stato creato finora. Se si usa Visual Studio, selezionare Avvia senza eseguire il debug dal menu Debug. Se si usa una riga di comando, digitare dotnet run nella directory in cui è stato creato il progetto.

Si è notato che il numero di conto è vuoto? È tempo di correggere questa mancanza. Il numero di conto deve essere assegnato al momento della costruzione dell'oggetto, ma la sua creazione non deve essere responsabilità del chiamante. Il codice della classe BankAccount deve sapere come assegnare nuovi numeri di conto. Un modo semplice consiste nell'iniziare con un numero di 10 cifre. e incrementarlo per la creazione di ogni nuovo conto. Infine, il numero di conto corrente deve essere archiviato quando viene costruito un oggetto.

Aggiungere una dichiarazione membro alla classe BankAccount. Inserire la riga di codice seguente dopo la parentesi graffa di apertura { all'inizio della classe BankAccount:

private static int s_accountNumberSeed = 1234567890;

accountNumberSeed un membro dati. Il membro è private, ovvero è accessibile solo dal codice all'interno della classe BankAccount. Si tratta di un modo per separare le responsabilità pubbliche (ad esempio, la disponibilità di un numero di conto) dall'implementazione privata (il modo in cui vengono generati i numeri di conto). È anche static, ovvero è condiviso tra tutti gli oggetti BankAccount. Il valore di una variabile non statica è univoco per ogni istanza dell'oggetto BankAccount. accountNumberSeed è un campo private static e quindi ha il prefisso s_ in base alle convenzioni di denominazione C#. s denota static mentre _ denota il campo private. Aggiungere le due righe seguenti al costruttore per assegnare il numero di conto. Posizionarli dopo la riga che indica this.Balance = initialBalance:

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

Digitare dotnet run per visualizzare i risultati.

Creare versamenti e prelievi

La classe del conto bancario deve accettare versamenti e prelievi per funzionare correttamente. I versamenti e i prelievi vengono implementati tramite la creazione di un registro di ogni transazione per il conto. Il rilevamento di ogni transazione presenta alcuni vantaggi rispetto all'aggiornamento del saldo su ogni transazione. È possibile usare la cronologia per controllare tutte le transazioni e gestire i saldi giornalieri. Grazie alla possibilità di calcolare il saldo dalla cronologia di tutte le transazioni all'occorrenza, eventuali errori in una singola transazione risolti saranno rispecchiati correttamente nel saldo dopo il calcolo successivo.

Per iniziare, occorre creare un nuovo tipo per rappresentare una transazione. Si tratta di un tipo di transazione semplice senza responsabilità. che richiede alcune proprietà. Creare un nuovo file denominato Transaction.cs. Aggiungere alla classe il codice seguente:

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

A questo punto, aggiungere un List<T> di oggetti Transaction alla classe BankAccount. Aggiungere la dichiarazione seguente dopo il costruttore nel fileBankAccount.cs:

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

A questo punto, è possibile calcolare correttamente Balance. Il saldo attuale è disponibile sommando i valori di tutte le transazioni. Poiché il codice è attuale, è possibile ottenere solo il saldo iniziale dell'account, quindi sarà necessario aggiornare la proprietà Balance. Sostituire la riga public decimal Balance { get; } in BankAccount.cs con il codice seguente:

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

        return balance;
    }
}

Questo esempio illustra un aspetto importante delle proprietà. Il saldo viene ora calcolato quando un altro programmatore richiede il valore. Il calcolo enumera tutte le transazioni e fornisce la somma come saldo corrente.

Implementare ora i metodi MakeDeposit e MakeWithdrawal. Questi metodi applicheranno le due regole finali, ovvero il saldo iniziale deve essere positivo e qualsiasi prelievo non può risultare in un saldo negativo.

Queste regole introducono il concetto di eccezioni. Il modo standard per indicare che un metodo non può completare correttamente le operazioni previste consiste nel generare un'eccezione. Il tipo di eccezione e il messaggio associato descrivono l'errore. In questo caso, il metodo MakeDeposit genera un'eccezione se l'importo del deposito non è superiore a 0. Il metodo MakeWithdrawal genera un'eccezione se l'importo del prelievo non è superiore a 0 o se si applica il ritiro comporta un saldo negativo. Aggiungere il codice seguente dopo la dichiarazione dell'elenco di _allTransactions:

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

L'throwistruzionegenera un'eccezione. L'esecuzione del blocco corrente termina e il controllo viene trasferito al primo blocco catch corrispondente trovato nello stack di chiamate. Più avanti si aggiungerà un blocco catch per testare questo codice.

Il costruttore deve ottenere una modifica in modo da aggiungere una transazione iniziale, invece di aggiornare direttamente il saldo. Dato che il metodo MakeDeposit è già stato scritto, chiamarlo dal costruttore. Il costruttore completato dovrebbe essere simile al seguente:

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

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

DateTime.Now è una proprietà che restituisce la data e l'ora correnti. Testare questo codice aggiungendo alcuni depositi e prelievi nel metodo Main, seguendo il codice che crea un nuovo BankAccount:

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

Verificare poi che le condizioni di errore vengano intercettate, tentando di creare un conto con saldo negativo. Aggiungere il codice seguente dopo il codice precedente appena aggiunto:

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

Usare le try-catchistruzioni per contrassegnare un blocco di codice che può generare eccezioni e intercettare gli errori previsti. È possibile usare la stessa tecnica per testare il codice che genera un'eccezione per un saldo negativo. Aggiungere il codice seguente prima della dichiarazione di invalidAccount nel metodo 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());
}

Salvare il file e digitare dotnet run per provare il codice.

Esercizio: registrare tutte le transazioni

Per completare questa esercitazione, è possibile scrivere il metodo GetAccountHistory, che crea un oggetto string per la cronologia delle transazioni. Aggiungere questo metodo al tipo BankAccount:

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

La cronologia usa la classe StringBuilder per formattare una stringa che contiene una riga per ogni transazione. Il codice per la formattazione delle stringhe è stato presentato in precedenza in queste esercitazioni. \t è un nuovo carattere, che consente di inserire una tabulazione per formattare l'output.

Aggiungere questa riga per testarla in Program.cs:

Console.WriteLine(account.GetAccountHistory());

Eseguire il programma per vedere i risultati.

Passaggi successivi

In caso di problemi, è possibile visualizzare il codice sorgente per questa esercitazione nel repository GitHub.

È possibile continuare con l'esercitazione sulla programmazione orientata agli oggetti.

Maggiori informazioni su questi concetti sono disponibili in questi articoli: