ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 11.12.2025

Просмотров: 3179

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

// Deposit - позволен любой положительный вклад public voi d Deposit(doubl e dAmount)

{

if (dAmount > 0.0)

{

//Округление к ближайшей копейке перед внесением

//вклада

decimal mTemp = (decimal)dAmount; mTemp = Decimal . Round(mTemp , 2 ) ;

mBalance += m T e m p ;

}

// Withdraw - вы можете снять со счета любую сумму, не

//превышающую баланс ; функция возвращает реально снятую

//сумму

public decimal Withdraw(decima l dWithdrawal)

{

if (mBalance <= dWithdrawal)

{

dWithdrawal = m B a l a n c e ;

}

mBalance -= dWithdrawal ; return dWithdrawal ;

// 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 ( ) ,

 

 

GetBalance() ) ;

return

s;

 

Внутреннее представление поменялось на использование значений типа decimal, который в любом случае более подходит для работы с банковским счетом, чем тип dou ­ ble. Метод Deposit () теперь применяет функцию Decimal . Round () для округле­ ния вкладываемой суммы до ближайшей копейки. Вывод программы оказывается таким, как и ожидалось:

Создание

объекта

банковского счета

Вклад $123.45

 

 

 

Счет =

#1001

=

 

$123 .45

Вклад $0.00

 

 

 

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

=

#1001 = $123 .45

Нажмите <Enter>

для

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


Выводы

Вы можете сказать, что нужно было с самого начала писать программу Bank count с использованием decimal, и, пожалуй, с вами можно согласиться. Но дед этом. Могут быть разные приложения и ситуации. Главное, что класс BankAcoi оказался в состоянии решить проблему так, что не пришлось вносить никаких изма в использующую его программу (обратите внимание, что открытый интерфейс ш изменился: метод Balance () так и возвращает значение типа double).

Повторюсь еще раз: приложение, использующее класс BankAccount должно изменяться при изменении класса.

В данном случае единственной функцией, которую бы пришлось изменить при но средственном обращении к балансу, является функция Mai n ( ) , но в реальной про| ме могут иметься десятки таких функций, и они могут оказаться в не меньшем колн| ве модулей. В анализируемом примере ни одна из этих функций не требует внесегая менений, но если бы они непосредственно обращались ко внутренним членам класса было бы решительно невозможно.

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

Определение свойств класса

Методы G e t X O и SetX() , продемонстрированные в программе BankAccou называются функциями доступа (access functions). Хотя их использование теоретичи является хорошей привычкой, на практике это зачастую приводит к грустным резуж там. Судите сами — чтобы увеличить на 1 член nAccountNumber, требуется пис следующий код:

SetAccountNumber(GetAccountNumber() + 1 ) ;

С# имеет конструкцию, называемую свойством и делающую использование фунта доступа существенно более простым. Приведенный далее фрагмент кода определ свойство AccountNumbe r для чтения и записи:

public int AccountNumbe r

// Скобки не нужны

getjretur n nAccountNumber; }

// Фигурные скобки и точка с

 

//

запятой

set{nAccountNumber = value;}

//

value - ключевое слово

Раздел get реализуется при чтении свойства, a set — при записи. В приведена далее фрагменте исходного текста свойство Balance является свойством только д чтения, так как здесь определен только раздел get:

public double Balance

{

get

{

242

Часть

IV.

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


return (double)mBalance;

Использование свойств выглядит следующим образом:

BankAccount ba = ne w BankAccount () ;

//Записываем свойство AccountNumbe r ba. AccountNumber = 1001 ;

//Считываем оба свойства

Console. WriteLine ("#{ 0} = { l : C } " , ba .AccountNumber, ba.Balance) ;

Свойства AccountNumbe r и Balance очень похожи на открытые члены-данные как внешне, так и в использовании. Однако свойства позволяют классу защитить свои внутренние члены (так, член mBalanc e остается при этом private). Обратите внима­ ние, что Balance выполняет приведение типа— точно так же может производиться любое количество вычислений. Свойства вовсе не обязательно должны представлять со­ бой одну строку кода.

По соглашению имена свойств начинаются с прописной буквы. Обратите также внимание, что свойства не имеют скобок: следует писать просто Balance, а не

Balance().

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

Статические свойства

Статические члены-данные могут быть доступны через статические свойства, как по­ казано в следующем простейшем примере:

public class BankAccount

private static int nNextAccountNumbe r = 1 0 0 0 ; public static int NextAccountNumbe r

get {return nNextAccountNumber; }

Свойство NextAccountNumber доступно посредством указания имени его класса, так как оно не является свойством конкретного объекта.

// Считываем свойство NextAccountNumbe r

int nValue = BankAccount . NextAccountNumber;

Побочные действия свойств

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

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

243


p u b l ic static int AccountNumbe r

{

//

Получение значения переменной и увеличение

ее значения,

//

чтобы в следующий раз получить уже новое ее

значение

get{retur n ++nNextAccountNumber;}

}

Это свойство увеличивает статический член класса перед тем, как вернуть результат. Однако это не слишком умная идея, ведь пользователь ничего не знает о такой ocoбен ности и не подозревает, что происходит что-то помимо чтения значения. Увеличение ременной в данном случае представляет собой побочное действие.

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

Управление доступом — это только половина проблемы. Рождение объекта — один из самых важных этапов в его жизни. Класс, конечно, может предоставить метод до инициализации вновь созданного объекта, но беда в том, что приложение может попро сту забыть его вызвать. В таком случае члены-данные класса окажутся заполнен] "мусором", и корректной работы от такого объекта ждать не придется.

С# решает эту проблему путем вызова инициализирующей функции автоматичен

Например, в строке

MyObject mo = ne w M y O b j e c t O ;

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

Не путайте термины класс и объект. Dog — это класс, но собака Scooter-I это объект класса Dog.

С# хорошо умеет отслеживать инициализацию переменных и не позволяет исполин вать неинициализированные переменные. Например, представленный далее код приведя к генерации ошибки компиляции:

public static void Main(string[] args)

{

int n ; double d;

double dCalculatedValu e = n + d;

}

244

Часть IV.

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


С# отслеживает тот факт, что ни n, ни d не имеют присвоенного значения и не могут использоваться в выражении. Компиляция этой микропрограммы приводит к генерации следующих ошибок:

Use of unassigned local v a r i a b l e 'n'

Use of unassigned local v a r i a b l e 'd'

Однако C# предоставляет конструктор по умолчанию для объектов классов, который инициализирует члены-данные значением 0 для встроенных переменных, false — для логических и null — для ссылок. Рассмотрим следующую простую демонстрационную программу:

using System;

 

 

 

namespace

Test

 

 

 

(

 

 

 

 

public

class Program

 

 

{

 

 

 

 

public static

void M a i n (string []

args)

{

 

 

 

 

//

Сначала

создаем объект

 

 

MyObject localObject = n e w

M y O b j e c t O ;

Console . WriteLine("localObject . n = { o } " , l o c a l O b j e c t . n ) ;

if

(localObject.nextObject

==

null)

{

Console . WriteLine("localObject . nextObject = n u l l " ) ;

}

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

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

Console.Read();

public class MyObject

{

internal int n;

internal MyObject n e x t O b j e c t ;

Эта программа определяет класс MyObject, который содержит переменную п типа int и ссылку на объект nextO b j ect, позволяющую создавать связанные списки объек­ тов. Функция Main () создает объект класса MyO b j ect и выводит начальное содержи­ мое его членов. Вывод этой программы имеет вид

localObject .п = О

localObject .nextObject = null

Нажмите <Enter> для завершения программы ...

С# при создании объекта выполняет небольшой код по ишшиализации объекта и его чле­ нов. Если бы не этот код, члены-данные localOb j ect. п и localOb j ect. nextOb j ect содержали бы какие-то случайные значения, попросту говоря — "мусор".

[Сод, инициализирующий значения при создании, называется конструктором. Эн "конструирует" класс в смысле инициализации его членов.

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

245