Exploración de la programación orientada a objetos con clases y objetosExplore object oriented programming with classes and objects

En este tutorial se supone que cuenta con una máquina que puede usar para el desarrollo.This tutorial expects that you have a machine you can use for development. El tutorial de .NET Hola mundo en 10 minutos cuenta con instrucciones para configurar el entorno de desarrollo local en Windows, Linux o macOS.The .NET tutorial Hello World in 10 minutes has instructions for setting up your local development environment on Windows, Linux, or macOS. En Become familiar with the development tools (Familiarizarse con las herramientas de desarrollo) puede obtener información general sobre los comandos que usará, donde hay vínculos que amplían la información.A quick overview of the commands you'll use is in the Become familiar with the development tools with links to more details.

Creación de una aplicaciónCreate your application

En una ventana de terminal, cree un directorio denominado clases.Using a terminal window, create a directory named classes. Creará la aplicación ahí.You'll build your application there. Cambie a ese directorio y escriba dotnet new console en la ventana de la consola.Change to that directory and type dotnet new console in the console window. Este comando crea la aplicación.This command creates your application. Abra Program.cs.Open Program.cs. El resultado debería tener un aspecto similar a este:It should look like this:

using System;

namespace classes
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

En este tutorial, se van a crear tipos nuevos que representan una cuenta bancaria.In this tutorial, you're going to create new types that represent a bank account. Normalmente los desarrolladores definen cada clase en un archivo de texto diferente.Typically developers define each class in a different text file. De esta forma, la tarea de administración resulta más sencilla a medida que aumenta el tamaño del programa.That makes it easier to manage as a program grows in size. Cree un archivo denominado CuentaBancaria.cs en el directorio clases.Create a new file named BankAccount.cs in the classes directory.

Este archivo contendrá la definición de una cuenta bancaria.This file will contain the definition of a bank account. La programación orientada a objetos organiza el código mediante la creación de tipos en forma de clases.Object Oriented programming organizes code by creating types in the form of classes. Estas clases contienen el código que representa una entidad específica.These classes contain the code that represents a specific entity. La clase BankAccount representa una cuenta bancaria.The BankAccount class represents a bank account. El código implementa operaciones específicas a través de métodos y propiedades.The code implements specific operations through methods and properties. En este tutorial, la cuenta bancaria admite el siguiente comportamiento:In this tutorial, the bank account supports this behavior:

  1. Tiene un número de diez dígitos que identifica la cuenta bancaria de forma única.It has a 10-digit number that uniquely identifies the bank account.
  2. Tiene una cadena que almacena el nombre o los nombres de los propietarios.It has a string that stores the name or names of the owners.
  3. Se puede consultar el saldo.The balance can be retrieved.
  4. Acepta depósitos.It accepts deposits.
  5. Acepta reintegros.It accepts withdrawals.
  6. El saldo inicial debe ser positivo.The initial balance must be positive.
  7. Los reintegros no pueden resultar en un saldo negativo.Withdrawals cannot result in a negative balance.

Definición del tipo de cuenta bancariaDefine the bank account type

Puede empezar por crear los datos básicos de una clase que define dicho comportamiento.You can start by creating the basics of a class that defines that behavior. Cree un archivo con el comando File:New.Create a new file using the File:New command. Asígnele el nombre BankAccount.cs.Name it BankAccount.cs. Agregue el código siguiente al archivo BankAccount.cs:Add the following code to your BankAccount.cs file:

using System;

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

Antes de avanzar, se va a dar un repaso a lo que ha compilado.Before going on, let's take a look at what you've built. La declaración namespace permite organizar el código de forma lógica.The namespace declaration provides a way to logically organize your code. Este tutorial es relativamente pequeño, por lo que deberá colocar todo el código en un espacio de nombres.This tutorial is relatively small, so you'll put all the code in one namespace.

public class BankAccount define la clase o el tipo que va a crear.public class BankAccount defines the class, or type, you are creating. Todo lo que se encuentra entre { y } después de la declaración de clase define el estado y el comportamiento de la clase.Everything inside the { and } that follows the class declaration defines the state and behavior of the class. Hay cinco miembros de la clase BankAccount.There are five members of the BankAccount class. Los tres primeros son propiedades.The first three are properties. Las propiedades son elementos de datos que pueden contener código que exige la validación u otras reglas.Properties are data elements and can have code that enforces validation or other rules. Los dos últimos son métodos.The last two are methods. Los métodos son bloques de código que realizan una única función.Methods are blocks of code that perform a single function. La lectura de los nombres de cada miembro debe proporcionar suficiente información tanto al usuario como a otro desarrollador para entender cuál es la función de la clase.Reading the names of each of the members should provide enough information for you or another developer to understand what the class does.

Apertura de una cuenta nuevaOpen a new account

La primera característica que se va a implementar es la apertura de una cuenta bancaria.The first feature to implement is to open a bank account. Cuando un cliente abre una cuenta, debe proporcionar un saldo inicial y la información sobre el propietario o los propietarios de esa cuenta.When a customer opens an account, they must supply an initial balance, and information about the owner or owners of that account.

La creación de un objeto del tipo BankAccount conlleva definir un constructor que asigne dichos valores.Creating a new object of the BankAccount type means defining a constructor that assigns those values. Un constructor es un miembro que tiene el mismo nombre que la clase.A constructor is a member that has the same name as the class. Se utiliza para inicializar los objetos de ese tipo de clase.It is used to initialize objects of that class type. Agregue el siguiente constructor al tipo BankAccount.Add the following constructor to the BankAccount type. Coloque el siguiente código encima de la declaración de MakeDeposit.Place the following code above the declaration of MakeDeposit:

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

A los constructores se les llama cuando se crea un objeto mediante new.Constructors are called when you create an object using new. Reemplace la línea Console.WriteLine("Hello World!"); de Program.cs por la siguiente línea (reemplace <name> por su nombre):Replace the line Console.WriteLine("Hello World!"); in Program.cs with the following code (replace <name> with your name):

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

Vamos a ejecutar lo que se ha creado hasta ahora.Let's run what you've built so far. Si usa Visual Studio, seleccione Iniciar sin depurar en el menú Ejecutar.If you're using Visual Studio, Select Start without debugging from the Run menu. Si va a usar una línea de comandos, escriba dotnet run en el directorio en el que ha creado el proyecto.If you're using a command line, type dotnet run in the directory where you've created your project.

¿Ha observado que el número de cuenta está en blanco?Did you notice that the account number is blank? Es el momento de solucionarlo.It's time to fix that. El número de cuenta debe asignarse cuando se construye el objeto.The account number should be assigned when the object is constructed. Sin embargo, el autor de la llamada no es el responsable de crearlo.But it shouldn't be the responsibility of the caller to create it. El código de la clase BankAccount debe saber cómo asignar nuevos números de cuenta.The BankAccount class code should know how to assign new account numbers. Una manera sencilla de hacerlo es empezar con un número de diez dígitos.A simple way to do this is to start with a 10-digit number. Increméntelo cuando cree cada cuenta.Increment it when each new account is created. Por último, almacene el número de cuenta actual cuando se construya un objeto.Finally, store the current account number when an object is constructed.

Agregue una declaración de miembro a la clase BankAccount.Add a member declaration to the BankAccount class. Coloque la siguiente línea de código después de la llave de apertura { al principio de la clase BankAccount:Place the following line of code after the opening brace { at the beginning of the BankAccount class:

private static int accountNumberSeed = 1234567890;

Se trata de un miembro de datos.This is a data member. Tiene el estado private, lo que significa que solo se puede acceder a él con el código incluido en la clase BankAccount.It's private, which means it can only be accessed by code inside the BankAccount class. Es una forma de separar las responsabilidades públicas (como tener un número de cuenta) de la implementación privada (cómo se generan los números de cuenta).It's a way of separating the public responsibilities (like having an account number) from the private implementation (how account numbers are generated). También es static, lo que significa que se comparte entre todos los objetos BankAccount.It is also static, which means it is shared by all of the BankAccount objects. El valor de una variable no estática es único para cada instancia del objeto BankAccount.The value of a non-static variable is unique to each instance of the BankAccount object. Agregue las dos líneas siguientes al constructor para asignar el número de cuenta:Add the following two lines to the constructor to assign the account number. Colóquelas después de la línea donde pone this.Balance = initialBalance:Place them after the line that says this.Balance = initialBalance:

this.Number = accountNumberSeed.ToString();
accountNumberSeed++;

Escriba dotnet run para ver los resultados.Type dotnet run to see the results.

Creación de depósitos y reintegrosCreate deposits and withdrawals

La clase de la cuenta bancaria debe aceptar depósitos y reintegros para que el funcionamiento sea adecuado.Your bank account class needs to accept deposits and withdrawals to work correctly. Se van a implementar depósitos y reintegros con la creación de un diario de cada transacción de la cuenta.Let's implement deposits and withdrawals by creating a journal of every transaction for the account. Ofrece algunas ventajas con respecto al mero hecho de actualizar el saldo en cada transacción.That has a few advantages over simply updating the balance on each transaction. El historial se puede utilizar para auditar todas las transacciones y administrar los saldos diarios.The history can be used to audit all transactions and manage daily balances. Con el cálculo del saldo a partir del historial de todas las transacciones, cuando proceda, todos los errores de una única transacción que se solucionen se reflejarán correctamente en el saldo cuando se realice el siguiente cálculo.By computing the balance from the history of all transactions when needed, any errors in a single transaction that are fixed will be correctly reflected in the balance on the next computation.

Se va a empezar por crear un tipo para representar una transacción.Let's start by creating a new type to represent a transaction. Se trata de un tipo simple que no tiene ninguna responsabilidad.This is a simple type that doesn't have any responsibilities. Necesita algunas propiedades.It needs a few properties. Cree un archivo denominado Transaction.cs.Create a new file named Transaction.cs. Agregue el código siguiente a él:Add the following code to it:

using System;

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)
        {
            this.Amount = amount;
            this.Date = date;
            this.Notes = note;
        }
    }
}

Ahora se va a agregar List<T> de objetos Transaction a la clase BankAccount.Now, let's add a List<T> of Transaction objects to the BankAccount class. Agregue la siguiente declaración después del constructor en el archivo BankAccount.cs:Add the following declaration after the constructor in your BankAccount.cs file:

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

La clase List<T> requiere la importación de un espacio de nombres diferente.The List<T> class requires you to import a different namespace. Agregue lo siguiente al principio de CuentaBancaria.cs:Add the following at the beginning of BankAccount.cs:

using System.Collections.Generic;

Ahora se va a cambiar la forma en que se notifica Balance.Now, let's change how the Balance is reported. Esto se puede conseguir con la suma de los valores de todas las transacciones.It can be found by summing the values of all transactions. Modifique la declaración de Balance en la clase BankAccount por lo siguiente:Modify the declaration of Balance in the BankAccount class to the following:

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

        return balance;
    }
}

En este ejemplo se muestra un aspecto importante de las propiedades.This example shows an important aspect of properties. Ahora va a calcular el saldo cuando otro programador solicite el valor.You're now computing the balance when another programmer asks for the value. El cálculo enumera todas las transacciones y proporciona la suma como el saldo actual.Your computation enumerates all transactions, and provides the sum as the current balance.

Después, implemente los métodos MakeDeposit y MakeWithdrawal.Next, implement the MakeDeposit and MakeWithdrawal methods. Estos métodos exigirán las dos reglas finales: el saldo inicial debe ser positivo y ningún reintegro debe generar un saldo negativo.These methods will enforce the final two rules: that the initial balance must be positive, and that any withdrawal must not create a negative balance.

Esta operación introduce el concepto de las excepciones.This introduces the concept of exceptions. La forma habitual de indicar que un método no puede completar su trabajo correctamente consiste en generar una excepción.The standard way of indicating that a method cannot complete its work successfully is to throw an exception. El tipo de excepción y el mensaje asociado a ella describen el error.The type of exception and the message associated with it describe the error. En este caso, el método MakeDeposit genera una excepción si el importe del depósito es negativo.Here, the MakeDeposit method throws an exception if the amount of the deposit is negative. El método MakeWithdrawal genera una excepción si la cantidad retirada es negativa o si la aplicación del reintegro tiene como resultado un saldo negativo:The MakeWithdrawal method throws an exception if the withdrawal amount is negative, or if applying the withdrawal results in a negative balance. Agregue el código siguiente después de la declaración de la lista allTransactions:Add the following code after the declaration of the allTransactions list:

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

La instrucción throwgenera una excepción.The throw statement throws an exception. La ejecución del bloque actual finaliza y el control se transfiere al primer bloque catch coincidente que se encuentra en la pila de llamadas.Execution of the current block ends, and control transfers to the first matching catch block found in the call stack. Se agregará un bloque catch para probar este código un poco más adelante.You'll add a catch block to test this code a little later on.

El constructor debe obtener un cambio para que agregue una transacción inicial, en lugar de actualizar el saldo directamente.The constructor should get one change so that it adds an initial transaction, rather than updating the balance directly. Puesto que ya escribió el método MakeDeposit, llámelo desde el constructor.Since you already wrote the MakeDeposit method, call it from your constructor. El constructor terminado debe tener este aspecto:The finished constructor should look like this:

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

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

DateTime.Now es una propiedad que devuelve la fecha y hora actuales.DateTime.Now is a property that returns the current date and time. Para probar esto, agregue algunos depósitos y reintegros en el método Main, a continuación del código que crea un elemento BankAccount:Test this by adding a few deposits and withdrawals in your Main method, following the code that creates a new 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);

Después, compruebe si detecta las condiciones de error al tratar de crear una cuenta con un saldo negativo.Next, test that you are catching error conditions by trying to create an account with a negative balance. Agregue el código siguiente después del código anterior que acaba de agregar:Add the following code after the preceding code you just added:

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

Use las instrucciones try y catch para marcar un bloque de código que puede generar excepciones y para detectar los errores que se esperan.You use the try and catch statements to mark a block of code that may throw exceptions and to catch those errors that you expect. Puede usar la misma técnica para probar el código que genera una excepción para un saldo negativo.You can use the same technique to test the code that throws an exception for a negative balance. Agregue el código siguiente al final del método Main:Add the following code at the end of your Main method:

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

Guarde el archivo y escriba dotnet run para probarlo.Save the file and type dotnet run to try it.

Desafío: registro de todas las transaccionesChallenge - log all transactions

Para finalizar este tutorial, puede escribir el método GetAccountHistory que crea string para el historial de transacciones.To finish this tutorial, you can write the GetAccountHistory method that creates a string for the transaction history. Agregue este método al tipo BankAccount:Add this method to the 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();
}

Usa la clase StringBuilder para dar formato a una cadena que contiene una línea para cada transacción.This uses the StringBuilder class to format a string that contains one line for each transaction. Se ha visto anteriormente en estos tutoriales el código utilizado para dar formato a una cadena.You've seen the string formatting code earlier in these tutorials. Un carácter nuevo es \t.One new character is \t. Inserta una pestaña para dar formato a la salida.That inserts a tab to format the output.

Agregue esta línea para probarla en Program.cs:Add this line to test it in Program.cs:

Console.WriteLine(account.GetAccountHistory());

Ejecute el programa para ver los resultados.Run your program to see the results.

Pasos siguientesNext steps

Si se ha quedado bloqueado, puede consultar el origen de este tutorial en el repositorio de GitHub.If you got stuck, you can see the source for this tutorial in our GitHub repo.

Puede continuar con el tutorial de la programación orientada a objetos.You can continue with the object oriented programming tutorial.

Puede aprender más sobre estos conceptos en los artículos siguientes:You can learn more about these concepts in these articles: