Как правило, банковские счета существуют не сами по себе, а внутри банка, который выступает некоторым контейнером счетов и выполняет функции по управлению ими. Поэтому добавим в проект BankLibrary новый класс Bank:
using BankLibrary; // тип счета public enum AccountType { Ordinary, Deposit } public class Bank<T> where T : Account { T[] accounts = { }; public string Name { get; } public Bank(string name) { Name = name; } // метод создания счета public void Open(AccountType accountType, decimal sum, AccountStateHandler addSumHandler, AccountStateHandler withdrawSumHandler, AccountStateHandler calculationHandler, AccountStateHandler closeAccountHandler, AccountStateHandler openAccountHandler) { T? newAccount = null; switch (accountType) { case AccountType.Ordinary: newAccount = new DemandAccount(sum, 1) as T; break; case AccountType.Deposit: newAccount = new DepositAccount(sum, 40) as T; break; } if (newAccount == null) throw new Exception("Ошибка создания счета"); // добавляем новый счет в массив счетов T[] tempAccounts = new T[accounts.Length + 1]; for (int i = 0; i < accounts.Length; i++) tempAccounts[i] = accounts[i]; tempAccounts[tempAccounts.Length - 1] = newAccount; accounts = tempAccounts; // установка обработчиков событий счета newAccount.Added += addSumHandler; newAccount.Withdrawed += withdrawSumHandler; newAccount.Closed += closeAccountHandler; newAccount.Opened += openAccountHandler; newAccount.Calculated += calculationHandler; newAccount.Open(); } //добавление средств на счет public void Put(decimal sum, int id) { T? account = FindAccount(id); if (account == null) throw new Exception("Счет не найден"); account.Put(sum); } // вывод средств public void Withdraw(decimal sum, int id) { T? account = FindAccount(id); if (account == null) throw new Exception("Счет не найден"); account.Withdraw(sum); } // закрытие счета public void Close(int id) { int index; T? account = FindAccount(id, out index); if (account == null) throw new Exception("Счет не найден"); account.Close(); if ( accounts.Length <= 1) accounts = new T[]{ }; else { // уменьшаем массив счетов, удаляя из него закрытый счет T[] tempAccounts = new T[accounts.Length - 1]; for (int i = 0, j = 0; i < accounts.Length; i++) { if (i != index) tempAccounts[j++] = accounts[i]; } accounts = tempAccounts; } } // начисление процентов по счетам public void CalculatePercentage() { for (int i = 0; i < accounts.Length; i++) { accounts[i].IncrementDays(); accounts[i].Calculate(); } } // поиск счета по id public T? FindAccount(int id) { for (int i = 0; i < accounts.Length; i++) { if (accounts[i].Id == id) return accounts[i]; } return null; } // перегруженная версия поиска счета public T? FindAccount(int id, out int index) { index = -1; for (int i = 0; i < accounts.Length; i++) { if (accounts[i].Id == id) { index = i; return accounts[i]; } } return null; } }
Класс банка является обобщенным. При этом параметр T имеет ограничение: он обязательно должен представлять класс Account или его наследников. Поэтому у любого объекта T нам будут доступны методы и свойства класса Account.
Все счета в классе хранятся в массиве accounts
. На момент проектирования класса мы можем не знать, какими именно счетами будет управлять банк.
Возможно, это будут любые счета, а может быть только депозитные, то есть объекты DepositAccount. Поэтому использование обобщений позволяет добавить больше гибкости.
При создании нового счета в методе Open
в этот метод передается ряд параметров, в частности, тип счета, который описывается перечислением:
public enum AccountType { Ordinary, Deposit }
Данное перечисление можно определить после класса Bank. В зависимости от типа счета создается объект DemandAccount или DepositAccount и затем добавляется в массив accounts. Поскольку массивы не расширяются автоматически, то фактически мы создаем новый массив с увеличением элементов на единицу и в конец нового массива добавляем новый элемент.
При этом параметризация, то есть создание обобщенных классов имеет ограничение в том, что созданный объект еще необходимо привести к типу T:
newAccount = new DemandAccount(sum, 1) as T;
Такое приведение позволит нам избежать ошибок, например, если мы типизируем класс Bank не Account, а типом DepositAccount, то преобразование
newAccount = new DemandAccount(sum, 1) as T
вернет нам значение null. Дальше мы можем проверить полученное значение на null:
if (newAccount == null)
Также в метод Open передаются обработчики для всех событий класса Account, которые устанавливаются после создания объекта Account.
В конце вызывается у нового объекта Account метод OnOpened()
, который генерирует событие Account.Opened, благодаря чему извне можно получить уведомление о событии.
Для поиска счета в массиве по id определяется метод FindAccount()
. Его перегруженная версия позволяет также получать индекс найденного элемента через выходной параметр
index:
public T? FindAccount(int id, out int index)
В методах Put, Withdraw и Close используются метод FindAccount()
для получения счета для добавления или вывода средств, а также закрытия.
При закрытии счета в методе Close()
создается новый массив без одного элемента - счета, который надо удалить. Таким образом, происходит удаление счета.
В методе CalculatePercentage()
пробегаемся по всем элементам массива счетов, увеличиваем у каждого счета счетчик дней и производим начисление процентов.
В общем класс Bank является оберткой, через которую из главного проекта мы будем взаимодействовать с объектами Account.
В тоге проект BankLibrary должен выглядеть следующим образом:
И теперь построим проект. Для этого нажмем на название проекта в окне Solution Explorer (Обозреватель решений) правой кнопкой мыши и в появившемся контекстном меню выберем пункт Build. После этого в проекте в папке bin/Debug будет создан файл библиотеки классов с расширением dll.