1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 45
Текст из файла (страница 45)
Затем функция M a i n () вносит на счет еще однудолю копейки и выводит баланс счета.Вывод программы выглядит следующим образом:Создание о б ъ е к т а б а н к о в с к о г о с ч е т аВклад $123 .45Счет = #1001 = $ 1 2 3 .45Вклад $0.00В р е з у л ь т а т е с ч е т = #1001 = $123.46Нажмите < E n t e r > д л я з а в е р ш е н и я п р о г р а м м ы . .
.Пользователь начинает жаловаться на некорректные расчеты. Лично я ничего неимею против округления счета до ближайшей сотни сверху, но ведь не все клиенты такиепокладистые... В общем, что греха таить — в программе действительно имеется ошибка.Проблема, конечно, в том, что 123.454 выводится как 123.45.
Чтобы избежать проблем, банк принимает решение округлять вклады и снятия до ближайшей копейки. Простейший путь осуществить это — конвертировать счета в d e c i m a l и использовать метод D e c i m a l . R o u n d ( ) , как это сделанов демонстрационной программе D e c i m a l B a n k A c c o u n t .// DecimalBankAccount - с о з д а н и е б а н к о в с к о г о с ч е т а с// и с п о л ь з о в а н и е м п е р е м е н н о й т и п а decimal д л я х р а н е н и я// б а л а н с а с ч е т аusing S y s t e m ;namespaceDecimalBankAccount{publicclassProgram{publicstatic void Main(string[]args){// Открытие б а н к о в с к о г о с ч е т аConsole.WriteLine("Создание о б ъ е к т а " +"банковскогосчета");BankAccount ba = n e w B a n k A c c o u n t ( ) ;ba.InitBankAccount();// Вклад на счетdouble dDeposit = 123.454;Console.WriteLine("Вклад {0:C}",dDeposit);ba.Deposit(dDeposit);//Баланс[пава 11.
Классысчета239Console.WriteLine("Счет = { o } "(ba.GetString());// Добавляем очень малую величинуdouble dAddition = 0.002;Console.WriteLine("Вклад {0:C}",dAddition);ba.Deposit(dAddition);// РезультатConsole.WriteLine("В результате счет =ba . G e t S t r i n g ()'');{ о } " ,// Ожидаем подтверждения пользователяConsole.WriteLine("Нажмите <Enter> для " +"завершения п р о г р а м м ы . . .
" ) ;Console.Read();}// BankAccount - определение класса, представляющего// простейший банковский счетpublic class BankAccount{p r i v a t e s t a t i c int n N e x t A c c o u n t N u m b e r = 1 0 0 0 ;p r i v a t e int n A c c o u n t N u m b e r ;// хранение баланса в видеprivate decimal mBalance;//////одной переменной типа decimalInit - инициализация банковского счета с нулевымбалансом и использованием очередного глобальногономераpublicvoidInitBankAccount(){nAccountNumber =m B a l a n c e = 0;++nNextAccountNumber;}// G e t B a l a n c epublic doublereturn- получение текущего балансаGetBalance()(double)mBalance;// AccountNumberp u b l i c int G e t A c c o u n t N u m b e r ( ){returnnAccountNumber;}publicvoidSetAccountNumber(intthis.nAccountNumber=nAccountNumber)nAccountNumber;}240Часть IV. Объектно-ориентированное программированиеГлава// D e p o s i t - п о з в о л е н л ю б о й п о л о ж и т е л ь н ы й в к л а дpublic v o i d D e p o s i t ( d o u b l e d A m o u n t ){if{(dAmount>0.0)// Округление к ближайшей копейке// вкладаdecimal mTemp = (decimal)dAmount;mTemp = Decimal.Round(mTemp, 2 ) ;mBalanceперед внесением+= m T e m p ;}// Withdraw - вы м о ж е т е с н я т ь со счета л ю б у ю с у м м у , не// превышающую б а л а н с ; функция возвращает р е а л ь н о снятую// суммуpublic d e c i m a l W i t h d r a w ( d e c i m a l d W i t h d r a w a l ){if( m B a l a n c e <= d W i t h d r a w a l ){dWithdrawal= mBalance;}mBalance -= d W i t h d r a w a l ;return d W i t h d r a w a l ;// G e t S t r i n g - в о з в р а щ а е т и н ф о р м а ц и ю о// виде с т р о к иpublic s t r i n g G e t S t r i n g ( )состоянии счета вstring s = S t r i n g .
F o r m a t ( " # { 0 } = { l : C } " ,GetAccountNumber(),GetBalance() ) ;return s;Внутреннее представление поменялось на использование значений типа d e c i m a l ,который в любом случае более подходит для работы с банковским счетом, чем тип d o u ble. Метод D e p o s i t () теперь применяет функцию D e c i m a l . R o u n d () для округления вкладываемой суммы до ближайшей копейки. Вывод программы оказывается таким,как и ожидалось:Создание о б ъ е к т а б а н к о в с к о г о с ч е т аВклад $123.45Счет = #1001=$123 .45Вклад $0.00В результате с ч е т = # 1 0 0 1 = $ 1 2 3 .
4 5Нажмите <Enter> д л я з а в е р ш е н и я п р о г р а м м ы . . .ВыводыВы можете сказать, что нужно было с самого начала писать программу Bankc o u n t с использованием d e c i m a l , и, пожалуй, с вами можно согласиться. Но дедэтом. Могут быть разные приложения и ситуации.
Главное, что класс BankAcoiоказался в состоянии решить проблему так, что не пришлось вносить никаких измав использующую его программу (обратите внимание, что открытый интерфейс шизменился: метод B a l a n c e () так и возвращает значение типа d o u b l e ) .Повторюсь еще раз: приложение, использующее класс B a n k A c c o u n tдолжно изменяться при изменении класса.В данном случае единственной функцией, которую бы пришлось изменить при носредственном обращении к балансу, является функция M a i n ( ) , но в реальной про|ме могут иметься десятки таких функций, и они могут оказаться в не меньшем колн|ве модулей.
В анализируемом примере ни одна из этих функций не требует внесегаяменений, но если бы они непосредственно обращались ко внутренним членам классабыло бы решительно невозможно.Внесение внутренних изменений в класс требует определенного тести рощиспользующего класс кода несмотря на то, что в него не вносятся никакие*дификации.Определение свойств классаМетоды G e t X O и S e t X ( ) , продемонстрированные в программе BankAccouназываются функциями доступа (access functions).
Хотя их использование теоретичиявляется хорошей привычкой, на практике это зачастую приводит к грустным резужтам. Судите сами — чтобы увеличить на 1 член n A c c o u n t N u m b e r , требуется писследующий код:SetAccountNumber(GetAccountNumber() + 1 ) ;С# имеет конструкцию, называемую свойством и делающую использование фунтадоступа существенно более простым. Приведенный далее фрагмент кода определсвойство A c c o u n t N u m b e r для чтения и записи:p u b l i c int A c c o u n t N u m b e r// Скобки не нужныgetjreturn nAccountNumber;}set{nAccountNumber = value;}// Ф и г у р н ы е скобки и точка с// запятой// v a l u e - к л ю ч е в о е словоРаздел g e t реализуется при чтении свойства, a s e t — при записи. В приведенадалее фрагменте исходного текста свойство B a l a n c e является свойством только дчтения, так как здесь определен только раздел g e t :publicdoubleBalance{get{242ЧастьIV.Объектно-ориентированноепрограммироваreturn(double)mBalance;Использование свойств выглядит следующим образом:BankAccount ba = n e w B a n k A c c o u n t () ;// Записываем с в о й с т в о A c c o u n t N u m b e rba.
AccountNumber = 1 0 0 1 ;// Считываем о б а с в о й с т в аConsole. W r i t e L i n e ("#{ 0} = { l : C } " , ba . A c c o u n t N u m b e r ,ba.Balance) ;Свойства A c c o u n t N u m b e r и B a l a n c e очень похожи на открытые члены-данныекак внешне, так и в использовании.
Однако свойства позволяют классу защитить своивнутренние члены (так, член m B a l a n c e остается при этом p r i v a t e ) . Обратите внимание, что B a l a n c e выполняет приведение т и п а — точно так же может производитьсялюбое количество вычислений. Свойства вовсе не обязательно должны представлять собой одну строку кода.По соглашению имена свойств начинаются с прописной буквы. Обратите такжевнимание, что свойства не имеют скобок: следует писать просто B a l a n c e , а неBalance().Свойства совсем не обязательно неэффективны. Компилятор С# может оптимизировать простую функцию доступа так, что она будет генерировать небольше машинных команд, чем непосредственное обращение к члену.
Этоважно не только для прикладных программ, но и для самого С#. Библиотека С#широко использует свойства, и то же должны делать и вы — даже для обращения к членам-данным класса из методов этого же класса.Статические свойстваСтатические члены-данные могут быть доступны через статические свойства, как показано в следующем простейшем примере:public class B a n k A c c o u n tprivate s t a t i c int n N e x t A c c o u n t N u m b e r =public s t a t i c i n t N e x t A c c o u n t N u m b e r1000;get {return n N e x t A c c o u n t N u m b e r ; }Свойство N e x t A c c o u n t N u m b e r доступно посредством указания имени его класса,так как оно не является свойством конкретного объекта.// Считываем с в о й с т в о N e x t A c c o u n t N u m b e rint nValue = B a n k A c c o u n t . N e x t A c c o u n t N u m b e r ;Побочные действия свойствОперация get может выполнять дополнительную работу помимо простого получениязначения, связанного со свойством.
Взгляните на следующий код:Глава 11. Классы243p u b l i c static int A c c o u n t N u m b e r{// П о л у ч е н и е з н а ч е н и я п е р е м е н н о й и у в е л и ч е н и е ее значения,// чтобы в следующий раз получить уже новое ее значениеget{return ++nNextAccountNumber;}}Это свойство увеличивает статический член класса перед тем, как вернуть результат.Однако это не слишком умная идея, ведь пользователь ничего не знает о такой ocoбенности и не подозревает, что происходит что-то помимо чтения значения. Увеличениеременной в данном случае представляет собой побочное действие.Подобно функциям доступа, которые они имитируют, свойства не должныйменять состояния класса иначе чем через установку значения соответствующего члена данных. В общем случае и свойства, и методы должны избегатьбочных действий, так как это может привести к трудноуловимым ошибкиИзменяйте класс настолько явно и непосредственно, насколько это возможно,Управление доступом — это только половина проблемы.