Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C# для чайников

.pdf
Скачиваний:
183
Добавлен:
27.03.2015
Размер:
15.52 Mб
Скачать

ние фарами оказалось там, где у всех "нормальных" автомобилей находится управление сигналами поворота. Отличие вроде бы небольшое, но я так и не научился поворачивать на этом автомобиле влево, не выключив при этом фары...

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

Микроволновая печь должна быть сконструирована таким образом, чтобы никакая комбинация кнопок или рукояток не могла ее сломать или навредить вам. Конечно, оп­ ределенные комбинации не будут выполнять никаких функций, но главное, чтобы ни од­ на из них не привела к следующему.

Поломке устройства. Какие бы рукоятки ни крутил ваш ребенок и какие бы кнопки не нажимал — микроволновая печь не должна от этого сломаться. После того как вы вернете все элементы управления в корректное состояние, она должна нормально работать.

К пожару или прочей порче имущества или нанесению вреда здоровью по­ требителя. Мы живем в сутяжном мире, и если бы что-то похожее могло про­ изойти — компании пришлось бы продать все вплоть до автомобиля ее президен­ та, чтобы рассчитаться с подающими на нее в суд и адвокатами.

Однако чтобы эти два правила выполнялись, вы должны принять на себя определен­ ную ответственность. Вы ни в коем случае не должны вносить изменения в устройство, в частности, отключать блокировки.

Почти все кухонное оборудование любой степени сложности, включая микроволно­ вые печи, имеет пломбы, препятствующие проникновению пользователя внутрь. Если такая пломба повреждена, это указывает, что крышка устройства была снята, и вся от­ ветственность с производителя тем самым снимается. Если вы каким-либо образом из­ менили внутреннее устройство печи, вы сами несете ответственность за все последую­ щие неприятности, которые могут произойти.

Аналогично, класс должен иметь возможность контролировать доступ к своим членамданным. Никакая последовательность вызовов членов класса не должна приводить программу к аварийному завершению, однако класс не в состоянии гарантировать это, если внешние объекты имеют доступ к внутреннему состоянию класса. Класс должен иметь возможность прятать критические члены-данные и делать их недоступными для внешнего мира.

Итак, как же С# реализует объектно-ориентированное программирование? Впрочем, это не совсем корректный вопрос. С# является объектно-ориентированным языком про-

Глава 10. Что такое объектно-ориентированное программирование

229

граммирования, но не реализует его — это делает программист. Как и на любом другом гом языке, вы можете написать на С# программу, не являющуюся объект! ориентированной (например, вставив весь код Word в функцию M a i n O ) . Иногда нужно писать и такие программы, но все же главное предназначение С# — создает объектно-ориентированных программ.

С# предоставляет программисту следующие необходимые для написания объект! ориентированных программ возможности.

Управляемый доступ. С# управляет обращением к членам класса. Ключе» слова С# позволяют объявить некоторые члены открытыми для всех, а другие защищенными или закрытыми. Подробнее эти вопросы рассматриваются в гла ве 11, "Классы".

Специализация. С# поддерживает специализацию посредством механизма, i вестного как наследование классов. Один класс при этом наследует члены друга класса. Например, вы можете создать класс Саг, как частный случай класса Vi h i c l e . Подробнее эти вопросы рассматриваются в главе 12, "Наследование".

Полиморфизм. Эта возможность позволяет объекту выполнить операцию так, как это требуется для его корректного функционирования. Например, класс Rocket унаследованный от V e h i c l e , может реализовать операцию S t a r t совершенно иначе, чем Саг, унаследованный от того же V e h i c l e . По крайней мере, будем надеяться, что это справедливо хотя бы по отношению к вашему автомобилю хотя с некоторыми автомобилями никогда ни в чем нельзя быть уверенным...| просьг полиморфизма рассматриваются в главах 13, "Полиморфизм", 1 "Интерфейсы и структуры".

230

Часть IV.

Объектно-ориентированное программировал

Глава 11

Классы

У Защита класса посредством управления доступом

>Инициализация объекта с помощью конструктора

>Определение нескольких конструкторов в одном классе

>Конструирование статических членов и членов класса

ласе должен сам отвечать за свои действия. Так же как микроволновая печь не должна вспыхнуть, объятая пламенем, из-за неверного нажатия кнопки, так и класс не должен скончаться (или прикончить программу) при предоставлении не­

корректных данных.

Чтобы нести ответственность за свои действия, класс должен убедиться в корректно­ сти своего начального состояния и в дальнейшем управлять им так, чтобы оно всегда ос­ тавалось корректным. С# предоставляет для этого все необходимое.

Простые классы определяют все свои члены как p u b l i c . Рассмотрим программу BankAccount, которая поддерживает член-данные b a l a n c e для хранения информа­ ции о балансе каждого счета. Сделав этот член p u b l i c , вы допускаете любого в святая святых банка, позволяя каждому самому указывать сумму на счету.

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

Управление доступом дает возможность избежать больших и малых ошибок в работе банка. Обычно программисты, привыкшие к функциональному про­ граммированию, говорят, что достаточно лишь определить правило, согласно которому никакие другие классы не должны обращаться к члену b a l a n c e не­ посредственно. Увы, теоретически это, может быть, и так, но на практике такой подход никогда не работает. Да, программисты начинают работу, будучи пере­ полненными благими намерениями, которые вскоре непонятно куда исчезают под давлением сроков сдачи проекта...

Пример программы с использованием открытых членов

В приведенной демонстрационной программе класс BankAccount объяв ляет все методы как public, в то же время члены-данные nAccountNumber и dBalance сделаны private. Эта демонстрационная программам

корректна и не будет компилироваться, так как создана исключительно в дидактических целях.

//

BankAccount - создание банковского счета с использованием

//

переменной типа double для хранения баланса

счета (она

//

объявлена как

p r i v a t e , чтобы скрыть баланс

от

внешнего

//

мира)

 

 

 

 

//

П р и м е ч а н и е : пока в программу не будут внесены

 

//

исправления,

она не будет

компилироваться,

так

как

//

функция Main() обращается

к private - член у класса

// B a n k A c c o u n t . usin g System;

namespace BankAccount

{

public class Program

{

public static v o i d Main(string[] args)

{

Console . WriteLine("В текущем состоянии эта " + "программа не к о м п и л и р у е т с я . " ) ;

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

Console . WriteLine("Создание объекта " +

 

"банковского

с ч е т а " ) ;

BankAccount ba = n e w B a n k A c c o u n t ( ) ;

b a . I n i t B a n k A c c o u n t ( ) ;

 

//

Обращение к балансу при помощи метода Deposit()

//

вполне к о р р е к т н о ; Deposit()

имеет право доступа ко

//всем членам - данным b a . D e p o s i t ( 1 0 ) ;

//Непосредственное обращение к члену - данны м вызывает

//ошибку компиляции

Console . WriteLine("Здесь вы получите " + "ошибку к о м п и л я ц и и " ) ;

ba . dBalance += 1 0 ;

// Ожидаем подтверждения пользователя Console . WriteLine("Нажмите <Enter> для " +

"завершения программы .. . ") ;

C o n s o l e . R e a d ( ) ;

} }

//BankAccount - определение класса, представляющего

//простейший банковский счет

public class BankAccount

232

Часть

IV.

Объектно-ориентированное

программирование

private static int n N e x t A c c o u n t N u m b e r = 10 0 0 ; private int n A c c o u n t N u m b e r ;

//хранение баланса в виде одной переменной типа double private double d B a l a n c e ;

//Init - инициализация банковского счета с нулевым

//балансом и использованием очередного глобального

//номера

public void InitBankAccount()

{

nAccountNumber = + + n N e x t A c c o u n t N u m b e r ; dBalance = 0.0;

// GetBalance - получение текущего баланса public double GetBalance()

{

return d B a l a n c e ;

// Номер счета

public int GetAccountNumber()

{

return n A c c o u n t N u m b e r ;

}

public void SetAccountNumber(int nAccountNumber)

{

this.nAccountNumber = n A c c o u n t N u m b e r ;

// Deposit - позволен любой положительный вклад public void D e p o s i t ( d o u b l e dAmount)

{

if (dAmount > 0.0)

{

dBalance += d A m o u n t ;

// Withdraw - вы можете

снять со счета любую сумму, не

// превышающую б а л а н с ;

функция возвращает реально снятую

// сумму

 

public double W i t h d r a w ( d o u b l e dWithdrawal)

{

if (dBalance <= dWithdrawal)

{

dWithdrawal = d B a l a n c e ;

dBalance -= d W i t h d r a w a l ; return d W i t h d r a w a l ;

Глава 11. Классы

233

private static int n N e x t A c c o u n t N u m b e r = 1 0 0 0 ; private int nAccountNuraber;

//хранение баланса в виде одной переменной типа double private double d B a l a n c e ;

//Init - инициализация банковского счета с нулевым

//балансом и использованием очередного глобального

//номера

public v o i d InitBankAccount()

nAccountNumber = + + n N e x t A c c o u n t N u m b e r ; dBalance = 0.0;

}

// GetBalance - получение текущего баланса public double GetBalance()

return d B a l a n c e ;

// Номер счета

public int GetAccountNumber( )

return n A c c o u n t N u m b e r ;

public void SetAccountNumber(in t nAccountNumber)

this . nAccountNumber = n A c c o u n t N u m b e r ;

// Deposit - позволен любой положительный вклад public void D e p o s i t ( d o u b l e dAmount)

{

if (dAmount > 0.0)

{

dBalance += d A m o u n t ;

//

Withdraw -

вы можете

снять

со счета любую сумму, не

//

превьшающую

б а л а н с ;

функция

возвращает реально снятую

//

сумму

 

 

 

public double W i t h d r a w ( d o u b l e dWithdrawal)

if (dBalance

<= dWithdrawal)

 

dWithdrawal = d B a l a n c e ;

dBalance -= d W i t h d r a w a l ; return d W i t h d r a w a l ;

233

// GetString

- возвращает информацию о состоянии счета в

// - виде

строки

public string

GetString()

{

 

 

string

s =

String . Format("#{0} = { l : C } " ,

 

 

G e t A c c o u n t N u m b e r ( ) ,

 

 

G e t B a l a n c e ( ) ) ;

retur n

s;

 

В этом коде выражение dBalance -= dWithdrawal означает то же, чтя

и dBalance = dBalance - dWithdrawal . Обычно программисты на C# стараются использовать наиболее короткую запись из возможных.

Объявляя член как public, вы делаете его доступным для любого кода вашей про­ граммы.

Класс BankAccount предоставляет метод InitBankAccount () для инициализа-1 ции членов класса, метод Deposit () — для обработки вкладов на счет и метод With-I draw ( ) — для снятия денег со счета. Методы Deposit ( ) и Withdra w ( ) даже обес-1 печивают выполнение некоторых рудиментарных правил — "нельзя вкладывать отрица-1 тельные суммы" и "нельзя снимать больше, чем есть на счету". Однако в открытой! системе, где член-данные dBalance доступен для внешних методов (под внешнтш подразумеваются методы "в пределах той же программы, но внешние по отношению! к классу"), эти правила могут быть нарушены кем угодно. Особенно существенной про-1 блемой это может оказаться при разработке больших проектов группами программистов.! Это может стать проблемой и для одного человека, поскольку ему свойственно ошибать-1 ся. Хорошо спроектированный код с правилами, выполнение которых проверяет компи-1 лятор, значительно снижает количество источников возможных ошибок.

Перед тем как идти дальше, обратите внимание, что приведенная демонстрационная про-1 грамма не будет компилироваться — при такой попытке вы получите сообщение о том, что! обращение к члену DoubleBankAccount. BankAccount. dBalance невозможно:

'DoubleBankAccount . BankAccount . dBalance' is inaccessible due to its p r o t e c t i o n level .

Трудно сказать, зачем компилятор заставили выводить такие скучные сообщения вместо короткого "не лезь к private", но суть именно в этом. Выражение ba . dBalance += 10; I оказывается некорректным именно по этой причине— в силу объявления dBalance как] private этот член недоступен виртуальной функции Main ( ) , расположенной вне класса BankAccount. Замена данного выражения на ba.Deposit (10) решает возникшую про­ блему— метод BankAccount. Deposit () объявлен как public, а потому доступен для функции Main ().

Тип доступа по умолчанию — private, так что если вы забыли или созна­ тельно пропустили модификатор для некоторого члена — это аналогично тому, как если бы вы описали его как private. Однако настоятельно рекомендуется всегда использовать это ключевое слово явно во избежание любых недоразу­

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

234

Часть

IV.

Объектно-ориентированное

программирование

Прочие уровни безопасности

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

С# предоставляет следующие уровни безопасности.

Члены, объявленные как public, доступны любому классу программы. Члены, объявленные как private, доступны только из текущего класса.

Члены, объявленные как protected, доступны только из текущего клас­ са и всех его подклассов.

Члены, объявленные как internal, доступны для любого класса в том же модуле программы.

Модулем в С# называется отдельно компилируемая часть кода, представ­ ляющая собой выполнимую . ЕХЕ-программу либо библиотеку . DLL. Од­ но пространство имен может распространяться на несколько модулей.

Члены, объявленные как internal protected, доступны для текуще­ го класса и всех его подклассов в том же модуле программы.

Скрытие членов путем объявления их как privat e обеспечивает максимальную степень безопасности. Однако зачастую такая высокая степень и не нужна. В конце концов, шы подклассов и так зависят от членов базового класса, так что ключевое слово p r o ­ jected предоставляет достаточно удобный уровень безопасности.

I Объявление внутренних членов класса как public — не лучшая мысль как минимум

по следующим причинам.

Объявляя члены-данные public , вы не в состоянии просто определить, когда и как они модифицируются. Зачем беспокоиться и создавать методы Deposit () и W i t h d r a w () с проверками корректности? И вообще, зачем соз­ давать любые методы — ведь любой метод любого класса может модифициро­ вать данные счета в любой момент. Но если другая функция может обращаться к этим данным, то она практически обязательно это сделает.

Ваша программа BankAccoun t может проработать длительное время, прежде чем вы заметите, что баланс одного из счетов— отрицателен. Метод With ­ draw () призван оградить от подобной ситуации, но в описанном случае непо­ средственный доступ к балансу, минуя метод W i t h d r a w n , имеют и другие функции. Вычислить, какие именно функции и при каких условиях поступают так некорректно — задача не из легких.

Доступ ко всем членам-данным класса делает его интерфейс слишком слож­ ным. Как программист, использующий класс BankAccount, вы не хотите знать о том, что делается внутри него. Вам достаточно знаний о том, как положить деньги на счет и снять их с него.

11. Классы

235

Доступ ко всем членам-данным класса приводит к "растеканию" права класса. Например, класс BankAccount не позволяет балансу стать отрицата ным ни при каких условиях. Это — бизнес-правило, которое должно быть локал зовано в методе Withdra w ( ) . В противном случае вам придется добавлять ш ветствующую проверку в весь код, в котором осуществляется изменение баланса,

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

Не делайте классы и методы более доступными, чем это необходимо. Этон параноидальная боязнь хакеров — это просто поможет вам снизить количесп ошибок в коде. По возможности используйте модификатор private, а зате при необходимости поднимайте его до protected, internal, internal protecte d или public.

Методы доступа

Если вы более внимательно посмотрите на класс BankAccount, то увидите несколь ко других методов. Один из них, GetStrin g ( ) , возвращает строковую версию счел для вывода ее на экран посредством функции Console . WriteLin e ( ) . Дело в том, чл вывод содержимого объекта BankAccount может быть затруднен, если это содержим» недоступно. К тому же, следуя принципу "отдайте кесарю кесарево", класс должен ими право сам решать, как он будет представлен при выводе.

Кроме того, имеется один метод для получения значения — GetBalance () и набо] методов для получения и установки значения — GetAccountNumbe r () и SetAc countNumber ( ) . Вы можете удивиться — зачем так волноваться из-за того, чтоб! член dBalance был объявлен как private, и при этом предоставлять метод GetBal апсе () ? На самом деле для этого имеются достаточно веские основания.

GetBalance О не дает возможности изменять член dBalance — он тольк возвращает его значение. Тем самым значение баланса делается доступны только для чтения. Используя аналогию с настоящим банком, вы можете просмот­ реть состояние своего счета в любой момент, но не можете снять с него деньги иначе, чем с применением процедур, предусмотренных для этого банком.

Метод GetBalance () скрывает внутренний формат класса от внешних ме­ тодов. Метод GetBalanc e () может в процессе работы выполнять некоторые вычисления, обращаться к базе данных банка — словом, выполнять какие-то дей­ ствия, чтобы получить состояние счета. Внешние функции ничего об этом не зна­ ют и не должны знать. Продолжая аналогию, вы интересуетесь состоянием счета но не знаете, как, где и в каком именно виде хранятся ваши деньги.

И наконец, метод GetBalanc e () предоставляет механизм для внесения внутрення! изменений в класс BankAccount, абсолютно не затрагивая при этом его пользователе!,! Если от нацбанка придет распоряжение хранить деньги как-то иначе, это никак не долх-1 но сказаться на вашем способе обращения с вашим счетом (по крайней мере так должно! быть в цивилизованном обществе).

236

Часть IV.

Объектно-ориентированное программирован

Пример управления доступом

Приведенная далее демонстрационная программа DoubleBankAccount указывает потенциальные изъяны программы BankAccount .

//

DoubleBankAccount

- с о з д а н и е

б а н к о в с к о г о

с ч е т а

с

/ /

и с п о л ь з о в а н и е м п е р е м е н н о й т и п а double д л я

х р а н е н и я

 

баланса

с ч е т а ( о н а

о б ъ я в л е н а

к а к private ,

ч т о б ы

с к р ы т ь

//

баланс

от в н е ш н е г о

м и р а )

 

 

 

using System;

namespace DoubleBankAccount

{

{

public class Program

 

 

public static v o i d Main(string[]

args)

{

 

 

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

 

C o n s o l e . W r i t e L i n e ( "Создани е

о б ъ е к т а " +

" б а н к о в с к о г о с ч е т а " ) ;

BankAccount ba = n e w B a n k A c c o u n t ( ) ;

b a . I n i t B a n k A c c o u n t ( ) ;

 

 

/ / В к л а д н а с ч е т

 

 

double dDeposit = 1 2 3 . 4 5 4 ;

 

 

C o n s o l e . W r i t e L i n e ( " В к л а д { 0 : C } " , d D e p o s i t ) ;

b a . D e p o s i t ( d D e p o s i t ) ;

 

 

/ / Б а л а н с с ч е т а

{ о } " ,

ba . GetString()) ;

C o n s o l e . W r i t e L i n e ( " С ч е т =

//Вот где имеетс я неприятность

double dAdditio n =

0.002;

 

C o n s o l e . W r i t e L i n e ( " В к л а д { 0 : C } " , d A d d i t i o n ) ;

b a . D e p o s i t ( d A d d i t i o n ) ;

 

 

/ / Р е з у л ь т а т

 

 

 

Console . WriteLine(

р е з у л ь т а т е с ч е т

= { о } " ,

 

b a . 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 ( "Нажмит е <Enter>

д л я " +

 

 

" з а в е р ш е н и я п р о г р а м м ы . . . " ) ;

C o n s o l e . R e a d ( ) ;

 

 

 

}

 

 

 

 

}

 

 

 

 

/ / BankAccount

- о п р е д е л е н и е

к л а с с а , п р е д с т а в л я ю щ е г о

/ / п р о с т е й ш и й б а н к о в с к и й с ч е т public class BankAccount

{

private static int n N e x t A c c o u n t N u m b e r = 1 0 0 0 ; private int n A c c o u n t N u m b e r ;

Глава 11. Классы

237

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]