Поделиться через


Исследуйте объектно-ориентированное программирование с помощью классов и объектов

В этом руководстве вы создадите консольное приложение и увидите основные объектно-ориентированные функции, которые являются частью языка C#.

Предварительные условия

Инструкции по установке

В Windows этот файл конфигурации WinGet используется для установки всех необходимых компонентов. Если у вас уже установлено что-то, WinGet пропустит этот шаг.

  1. Скачайте файл и дважды щелкните его, чтобы запустить его.
  2. Прочитайте лицензионное соглашение, введите и, и выберите ввод при появлении запроса на принятие.
  3. Если на панели задач появится мигающий запрос контроля учетных записей пользователей (UAC), разрешите установку продолжить.

На других платформах необходимо установить каждый из этих компонентов отдельно.

  1. Скачайте рекомендуемый установщик на странице загрузки пакета SDK для .NET и дважды щелкните его, чтобы запустить его. Страница загрузки обнаруживает платформу и рекомендует последний установщик для вашей платформы.
  2. Скачайте последнюю версию установщика на домашней странице Visual Studio Code и дважды щелкните его, чтобы запустить его. Эта страница также обнаруживает платформу, а ссылка должна быть правильной для вашей системы.
  3. Нажмите кнопку "Установить" на странице расширения C# DevKit. Откроется код Visual Studio и запрашивается, нужно ли установить или включить расширение. Выберите "Установить".

Создание приложения

С помощью окна терминала создайте каталог с именем "Классы". Вы создадите приложение там. Перейдите в этот каталог и введите dotnet new console в окне консоли. При помощи этой команды создается приложение. Откройте Program.cs. Он должен выглядеть так:

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

При работе с этим руководством вы создадите новые типы, представляющие банковский счет. Обычно разработчики определяют каждый класс в отдельном текстовом файле. Благодаря этому программой легче управлять, когда ее размер увеличивается. Создайте файл с именем BankAccount.cs в каталоге классов .

Этот файл содержит определение банковского счета. Средства объектно-ориентированного программирования обеспечивают упорядочение кода. При этом создаются типы в виде классов. Классы содержат код, который представляет отдельную сущность. Класс BankAccount представляет банковский счет. Этот код реализует определенные операции с помощью методов и свойств. В этом учебном пособии банковский счет поддерживает такое поведение:

  1. Представляет собой число из 10 цифр, которое однозначно определяет банковский счет.
  2. Содержит строку, в которой хранятся имена владельцев.
  3. Баланс можно получить.
  4. Принимает депозиты.
  5. Принимает выводы средств.
  6. Начальное сальдо должно было положительным.
  7. Вывод не может привести к отрицательному балансу.

Определение типа банковского счета

Сначала можно создать основы класса, который определяет такой режим работы. Создайте новый файл с помощью команды File:New. Присвойте ему имя BankAccount.cs. Добавьте в файл 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)
    {
    }
}

Прежде чем идти дальше, давайте рассмотрим то, что вы создали. Объявление namespace предоставляет способ логического упорядочения кода. Это руководство относительно небольшое, поэтому вы помещаете весь код в одно пространство имен.

public class BankAccount определяет класс или тип, который вы создаете. Весь код в скобках { и }, который следует за объявлением класса, определяет состояние и поведение класса. Есть пять элементов класса BankAccount. Первые три элемента представляют собой свойства. Свойства являются элементами данных и могут содержать код для выполнения проверки или других правил. Последние два элемента являются методами. Методы представляют собой блоки кода, которые выполняют только одну функцию. Чтение имен каждого из членов должно предоставить достаточно информации, чтобы вы или другой разработчик смогли понять, чем занимается класс.

Открытие нового счета

Сначала нужно открыть банковский счет. Когда клиент открывает счет, он должен указать начальное сальдо и сведения о владельцах этого счета.

Для создания нового объекта BankAccount типа требуется определить конструктор , который назначает эти значения. Конструктор — это элемент, имя которого совпадает с классом. Он используется для инициализации объектов этого типа класса. Добавьте указанный ниже конструктор в тип BankAccount. Добавьте следующий код непосредственно перед объявлением MakeDeposit:

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

Код, представленный выше, определяет свойства создаваемого объекта с использованием квалификатора this. Обычно этот квалификатор является необязательным и может быть опущен. Вы также можете написать:

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

Квалификатор this требуется только в том случае, если локальная переменная или параметр имеет то же имя, что и поле или свойство. Квалификатор this опущен в оставшейся части этой статьи, если это не необходимо.

Конструкторы вызываются при создании объекта с помощью new. Замените строку Console.WriteLine("Hello World!"); в файле Program.cs следующим кодом (замените <name> своим именем):

using Classes;

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

Давайте запустите то, что вы создали до сих пор. Если вы работаете в Visual Studio, выберите Запуск без отладки в меню Отладка. Если вы используете командную строку, введите dotnet run каталог, в котором вы создали проект.

Вы заметили, что номер счета не указан? Нужно решить эту проблему. Номер счета следует назначить при создании объекта. Но создавать этот номер не входит в обязанности вызывающего. Код класса BankAccount должен иметь информацию о том, как присвоить номера новым счетам. Простой способ — начать с 10-цифрного числа. Увеличивайте его при создании каждого нового счета. Затем при создании объекта сохраните номер текущего счета.

Добавьте объявление члена в класс BankAccount. Поместите следующую строку кода после открывающей скобки { в начале класса BankAccount:

private static int s_accountNumberSeed = 1234567890;

accountNumberSeed является элементом данных. Он имеет свойство private, то есть к нему может получить доступ только код внутри класса BankAccount. Этот метод разделяет публичные обязанности (например, наличие номера счета) и частную реализацию (как создаются номера счетов). Это также staticозначает, что все BankAccount объекты совместно используют один экземпляр этой переменной. Значение нестатической переменной является уникальным для каждого экземпляра объекта BankAccount. Поле accountNumberSeed является полем private statics_ и таким образом имеет префикс в соответствии с соглашениями об именовании C#. Поле, обозначающее sstatic и _, обозначающее private. Чтобы инициализировать каждый номер учетной записи, добавьте в конструктор следующие две строки. Они должны располагаться за строкой с текстом this.Balance = initialBalance.

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

Введите dotnet run, чтобы просмотреть результаты.

Создание депозитов и списаний

Для надлежащей работы ваш класс банковского счета должен принимать депозиты и списания. Чтобы реализовать депозиты и списания, создадим журнал для каждой транзакции на счете. Отслеживание каждой транзакции имеет несколько преимуществ по сравнению с простым обновлением баланса в каждой транзакции. Журнал можно использовать для аудита всех транзакций и управления ежедневным сальдо. Вычисление баланса из истории всех транзакций при необходимости гарантирует, что все ошибки в одной транзакции, исправленные, будут правильно отражены в балансе на следующем вычислении.

Начните с создания нового типа, представляющего транзакцию. Транзакция является типом record, который не имеет никаких обязанностей. Ему нужно несколько свойств. Создайте новый файл с именем Transaction.cs. Добавьте следующий код:

namespace Classes;

public record Transaction(decimal Amount, DateTime Date, string Notes);

Теперь добавим List<T> объектов Transaction в класс BankAccount. Добавьте следующее объявление после конструктора в ваш файл BankAccount.cs:

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

Далее мы соответствующим образом вычислим Balance. Чтобы вычислить текущий баланс, нужно суммировать значения всех транзакций. На данный момент с помощью кода можно получить только начальный баланс аккаунта, поэтому необходимо обновить свойство Balance. В файле public decimal Balance { get; } замените строку следующим кодом:

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

        return balance;
    }
}

В этом примере показан важный аспект свойств. Вы вычисляете сальдо, когда другой программист запрашивает значение. В результате вашего вычисления выводится список всех транзакций и сумма в виде текущего сальдо.

Теперь реализуйте методы MakeDeposit и MakeWithdrawal. Эти методы применяют последние два правила: начальный баланс должен быть положительным, и любой вывод не должен создавать отрицательный баланс.

Эти правила представляют концепцию исключений. Стандартный способ, указывающий, что метод не может успешно завершить свою работу, заключается в том, чтобы создать исключение. В типе исключения и связанном с ним сообщении описывается ошибка. Здесь метод MakeDeposit выбрасывает исключение, если сумма депозита не превышает 0. Метод MakeWithdrawal вызывает исключение, если сумма вывода не превышает 0, или если применение вывода приводит к отрицательному балансу. Добавьте следующий код после объявления списка _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);
}

throw создает исключение. Выполнение текущего блока завершается и управление передается в первый подходящий блок catch из стека вызовов. Вы добавите catch блок для тестирования этого кода немного позже.

Чтобы вместо непосредственного обновления сальдо добавлялась начальная транзакция, конструктор должен получить одно изменение. Так как вы уже написали метод MakeDeposit, вызовите его из конструктора. Готовый конструктор должен выглядеть так:

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

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

DateTime.Now — это свойство, которое возвращает текущие дату и время. Протестируйте этот код, добавив несколько депозитов и снятий в метод Main, следуя за кодом, который создает новый 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);

Затем проверьте, что вы обнаруживаете ошибочные состояния, пытаясь создать учетную запись с отрицательным балансом. Добавьте следующий код после только что добавленного блока кода:

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

Вы используете оператор try-catch для пометки блока кода, который может вызвать исключения и для перехвата ожидаемых ошибок. Так же можно проверять код, который вызывает исключение при получении отрицательного баланса. Добавьте следующий код перед объявлением invalidAccount в 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());
}

Сохраните файл и введите dotnet run для проверки.

Задача — регистрация всех транзакций

В завершение вы создадите метод GetAccountHistory, который создает string для журнала транзакций. Добавьте этот метод в тип 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();
}

История использует класс StringBuilder для форматирования строки, в которой содержится по одной строке для каждой транзакции. Вы использовали форматирование строк в предыдущих руководствах. Новый символ — это \t. Он позволяет вставить вкладку для форматирования выходных данных.

Добавьте следующую строку, чтобы проверить его в файле Program.cs:

Console.WriteLine(account.GetAccountHistory());

Запустите программу и проверьте результаты.

Следующие шаги

Если у вас возникли проблемы, изучите исходный код для этого руководства, размещенный в репозитории GitHub.

Вы можете продолжить, перейдя к учебнику по объектно-ориентированному программированию.

Дополнительные сведения об этих понятиях см. в следующих статьях: