1629295403-b876e2087bddebea4bc9666fb2377a02 (846199), страница 44
Текст из файла (страница 44)
Однако в открытой!системе, где член-данные d B a l a n c e доступен для внешних методов (под внешнтшподразумеваются методы "в пределах той же программы, но внешние по отношению!к классу"), эти правила могут быть нарушены кем угодно. Особенно существенной про-1блемой это может оказаться при разработке больших проектов группами программистов.!Это может стать проблемой и для одного человека, поскольку ему свойственно ошибать-1ся. Хорошо спроектированный код с правилами, выполнение которых проверяет компи-1лятор, значительно снижает количество источников возможных ошибок.Перед тем как идти дальше, обратите внимание, что приведенная демонстрационная про-1грамма не будет компилироваться — при такой попытке вы получите сообщение о том, что!обращение к члену D o u b l e B a n k A c c o u n t .
B a n k A c c o u n t . d B a l a n c e невозможно:'DoubleBankAccount.BankAccount.dBalance'due to its protection level.isinaccessibleТрудно сказать, зачем компилятор заставили выводить такие скучные сообщения вместокороткого "не лезь к private", но суть именно в этом. Выражение b a . d B a l a n c e += 10; Iоказывается некорректным именно по этой причине— в силу объявления d B a l a n c e как]p r i v a t e этот член недоступен виртуальной функции M a i n ( ) , расположенной вне классаB a n k A c c o u n t .
Замена данного выражения на b a . D e p o s i t (10) решает возникшую проб л е м у — метод B a n k A c c o u n t . D e p o s i t () объявлен как p u b l i c , а потому доступен дляфункции M a i n ().Тип доступа по умолчанию — p r i v a t e , так что если вы забыли или сознательно пропустили модификатор для некоторого члена — это аналогично тому,как если бы вы описали его как p r i v a t e .
Однако настоятельно рекомендуетсявсегда использовать это ключевое слово явно во избежание любых недоразумений. Хороший программист всегда явно указывает свои намерения, что является ещеодним методом снижения количества возможных ошибок.234ЧастьIV.Объектно-ориентированноепрограммированиеПрочие уровни безопасностиВ этом разделе используются определенные знания о наследовании и пространствах имен, которые будут рассмотрены в более поздних главах книги.Вы можете сейчас пропустить настоящий раздел и вернуться к нему позже, получив необходимые знания.С# предоставляет следующие уровни безопасности.Члены, объявленные как p u b l i c , доступны любому классу программы.Члены, объявленные как p r i v a t e , доступны только из текущего класса.Члены, объявленные как p r o t e c t e d , доступны только из текущего класса и всех его подклассов.Члены, объявленные как i n t e r n a l , доступны для любого класса в томже модуле программы.Модулем в С# называется отдельно компилируемая часть кода, представляющая собой выполнимую .
ЕХЕ-программу либо библиотеку . D L L . Одно пространство имен может распространяться на несколько модулей.Члены, объявленные как i n t e r n a l p r o t e c t e d , доступны для текущего класса и всех его подклассов в том же модуле программы.Скрытие членов путем объявления их как p r i v a t e обеспечивает максимальную степень безопасности. Однако зачастую такая высокая степень и не нужна. В конце концов,шы подклассов и так зависят от членов базового класса, так что ключевое слово p r o jected предоставляет достаточно удобный уровень безопасности.I Объявление внутренних членов класса как p u b l i c — не лучшая мысль как минимумпо следующим причинам.Объявляя члены-данные p u b l i c , вы не в состоянии просто определить,когда и как они модифицируются.
Зачем беспокоиться и создавать методыDeposit () и W i t h d r a w () с проверками корректности? И вообще, зачем создавать любые методы — ведь любой метод любого класса может модифицировать данные счета в любой момент. Но если другая функция может обращатьсяк этим данным, то она практически обязательно это сделает.Ваша программа B a n k A c c o u n t может проработать длительное время, преждечем вы заметите, что баланс одного из с ч е т о в — отрицателен.
Метод W i t h draw () призван оградить от подобной ситуации, но в описанном случае непосредственный доступ к балансу, минуя метод W i t h d r a w n , имеют и другиефункции. Вычислить, какие именно функции и при каких условиях поступаюттак некорректно — задача не из легких.Доступ ко всем членам-данным класса делает его интерфейс слишком сложным. Как программист, использующий класс B a n k A c c o u n t , вы не хотите знатьо том, что делается внутри него. Вам достаточно знаний о том, как положитьденьги на счет и снять их с него.'ш 11.
Классы235Доступ ко всем членам-данным класса приводит к "растеканию" правакласса. Например, класс B a n k A c c o u n t не позволяет балансу стать отрицатаным ни при каких условиях. Это — бизнес-правило, которое должно быть локалзовано в методе W i t h d r a w ( ) . В противном случае вам придется добавлять шветствующую проверку в весь код, в котором осуществляется изменение баланса,Что произойдет, когда банк решит изменить правила, и часть клиентов с хорошкредитной историей получит право на небольшой отрицательный баланс в тече!короткого времени? Вам придется долго рыскать по всей программе и вносиизменения во все места, где выполняется непосредственное обращение к балансу,Не делайте классы и методы более доступными, чем это необходимо. Этонпараноидальная боязнь хакеров — это просто поможет вам снизить количеспошибок в коде.
По возможности используйте модификатор p r i v a t e , а затепри необходимости поднимайте его до p r o t e c t e d , i n t e r n a l , internalp r o t e c t e d или p u b l i c .Методы доступаЕсли вы более внимательно посмотрите на класс B a n k A c c o u n t , то увидите несколько других методов. Один из них, G e t S t r i n g ( ) , возвращает строковую версию счелдля вывода ее на экран посредством функции C o n s o l e . W r i t e L i n e ( ) . Дело в том, члвывод содержимого объекта B a n k A c c o u n t может быть затруднен, если это содержим»недоступно.
К тому же, следуя принципу "отдайте кесарю кесарево", класс должен имиправо сам решать, как он будет представлен при выводе.Кроме того, имеется один метод для получения значения — G e t B a l a n c e () и набо]методов для получения и установки значения — G e t A c c o u n t N u m b e r () и SetAcc o u n t N u m b e r ( ) . Вы можете удивиться — зачем так волноваться из-за того, чтоб!член d B a l a n c e был объявлен как p r i v a t e , и при этом предоставлять метод GetBalа п с е () ? На самом деле для этого имеются достаточно веские основания.G e t B a l a n c e О не дает возможности изменять член d B a l a n c e — он тольквозвращает его значение. Тем самым значение баланса делается доступнытолько для чтения.
Используя аналогию с настоящим банком, вы можете просмотреть состояние своего счета в любой момент, но не можете снять с него деньгииначе, чем с применением процедур, предусмотренных для этого банком.Метод G e t B a l a n c e () скрывает внутренний формат класса от внешних методов. Метод G e t B a l a n c e () может в процессе работы выполнять некоторыевычисления, обращаться к базе данных банка — словом, выполнять какие-то действия, чтобы получить состояние счета.
Внешние функции ничего об этом не знают и не должны знать. Продолжая аналогию, вы интересуетесь состоянием счетано не знаете, как, где и в каком именно виде хранятся ваши деньги.И наконец, метод G e t B a l a n c e () предоставляет механизм для внесения внутрення!изменений в класс B a n k A c c o u n t , абсолютно не затрагивая при этом его пользователе!,!Если от нацбанка придет распоряжение хранить деньги как-то иначе, это никак не долх-1но сказаться на вашем способе обращения с вашим счетом (по крайней мере так должно!быть в цивилизованном обществе).236ЧастьIV.Объектно-ориентированноепрограммированПример управления доступомПриведенная далее демонстрационная программа D o u b l e B a n k A c c o u n tуказывает потенциальные изъяны программы B a n k A c c o u n t .// DoubleBankAccount// использованиембаланса// баланссчетаот-созданиепеременной(онавнешнегобанковскоготипаобъявленаdoubleкаксчетадлясхранениячтобыprivate,скрытьмира)using S y s t e m ;namespaceDoubleBankAccount{publicclassProgram{publicstatic void Main(string[]args){//ОткрытиебанковскогосчетаConsole.WriteLine("Созданиеобъекта"банковского"+с ч е т а " ) ;BankAccount ba = new B a n k A c c o u n t ( ) ;ba.InitBankAccount();//Вкладнасчетdouble dDeposit = 1 2 3 .
4 5 4 ;Console.WriteLine("Вклад {0:C}",ba.Deposit(dDeposit);//БалансdDeposit);счетаConsole.WriteLine("Счет = { о } " , ba.GetString()) ;// Вот где имеется неприятностьdouble dAddition = 0.002;Console.WriteLine("Вклад {0:C}", dAddition);ba.Deposit(dAddition);//РезультатConsole.WriteLine("Врезультатесчет={ о } " ,ba.GetString());//ОжидаемподтвержденияпользователяConsole.WriteLine("Нажмите <Enter> д л я"завершения"+программы.. . " ) ;Console.Read();}}//BankAccount//простейший-определениебанковскийкласса,представляющегосчетpublic c l a s s B a n k A c c o u n t{private static int n N e x t A c c o u n t N u m b e r =private int n A c c o u n t N u m b e r ;Глава 11. Классы1000;237// хранение баланса в видеprivate double dBalance;одной переменной типа double// I n i t - и н и ц и а л и з а ц и я б а н к о в с к о г о с ч е т а с н у л е в ы м// балансом и и с п о л ь з о в а н и е м очередного глобального•// н о м е р аpublicvoidInitBankAccount(){nAccountNumber =d B a l a n c e = 0.0;++nNextAccountNumber;}// GetBalancepublic double- получение текущего балансаGetBalance(){return dBalance;,}// 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(intnAccountNumber){this.nAccountNumber = nAccountNumber;-}// Deposit - позволен любой положительный вкладpublic void Deposit(double dAmount){if(dAmount>0.0){dBalance+= dAmount;}}// W i t h d r a w - вы м о ж е т е снять со счета любую сумму, не// превьшающую баланс; функция возвращает реально снятую// суммуpublic double Withdraw(double dWithdrawal){if(dBalance<= d W i t h d r a w a l ){dWithdrawal= dBalance;}d B a l a n c e -= d W i t h d r a w a l ;return dWithdrawal;}// G e t S t r i n g - в о з в р а щ а е т и н ф о р м а ц и ю о с о с т о я н и и с ч е т а в// виде строкиpublic string GetString()238ЧастьIV.Объектно-ориентированноепрограммирование{string s=String.Format("#{0} = {l:C}",GetAccountNumber(),GetBalance());return s;}}Функция M a i n ()} сумму с дробнымсоздает банковский счет и вносит на него сумму 123.454 — т.е.количеством копеек.