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


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

В этом руководстве показано, как создать консольное приложение, и приведены основные объектно-ориентированные функции языка 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, чтобы просмотреть результаты.

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

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

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

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

Теперь добавим 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.

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

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