Использование свойств С#
До сих пор проверочный код обращался к переменной-члену так:
els.ExchangeRate = 123.45;
Переменная-член была реализована следующим образом:
public abstract class CurrencyTrader
public double ExchangeRate;
Предоставление переменной-члена, с использованием области видимости public. вполне срабатывало в предыдущих главах, но здесь мы этого не хотим, поскольку не хо¬тим открывать внутреннее состояние объекта. В объектно-ориентированном програм¬мировании предоставление внутреннего состояния — плохая идея (я вскоре объясни подробнее, почему).
Вместо предоставления открытого доступа к переменной-члену, мы изменим прове¬рочный код так, чтобы использовались свойства. Свойства также предоставляют вну¬треннее состояние объекта, но они обеспечивают некоторый слой абстракции. Как вь: узнаете вскоре, некоторые свойства предоставляют и внутреннее, и внешнее состояние Так же обстоит дело и со свойством ExchangeRate, которое мы используем для доступа и изменения курса валют. Если мы не используем свойство ExchangeRate, то нужно создать метод, который присваивает курс валюты, и метод, который возвращает курс валюты. Методы вели бы себя подобно свойству, но их не так удобно использовать.
Перезапись проверочного кода для использования свойств
Интересный факт о свойствах С# — они выглядят и ведут себя подобно переменным-членам. Проверочный код для свойств С# не нужно переписывать, поскольку он подраз¬умевает прямой доступ к переменной. Рассмотрите на рис. 6.3 переписанный код метода : --rencyTrader, где переменная-член ExchangeRate представлена как свойство С#.
Чтобы полностью ввести в заблуждение проверку, имя свойства должно быть иден¬тично имени объявленной ранее переменной-члена. Для предотвращения возможного - : нфликта имен мы переименуем саму переменную-член ExchangeRate в exchangeRate
■ изменим ее область видимости с public на private.
Свойство выглядит как метод без параметров, но свойство имеет возвращаемое зна-тние. Каждое свойство должно также иметь по крайней мере блок кода get или блок
■ :да set (или оба), которые называются методом получения значения (getter) и мето-
"•:.'■; установки значения (setter). Если свойство имеет лишь блок кода get, то получает-
- свойство только для чтения. Если свойство имеет только блок кода set, вы создаете гзйство лишь для установки значения. Внутри контекста свойства вы можете иметь ": сько ключевое слово get или set. Нельзя добавлять никакой другой код.
Следующий код задействует код. расположенный в блоке кода get, и возвращает гекущее состояние переменной класса, value = els.ExchangeRate;
Следующий код задействует код, расположенный в блоке кода set, и присваивает состояние переменной класса.
els.Exchange = value;
Каждый блок кода имеет свои особенности. Блок кода get всегда должен возвращать данные вызывающей стороне и, таким образом, обязан использовать ключевое слово return. Блок кода set не обязан что-либо делать. Однако, если вы хотите знать, что данные переданы свойству, используйте переменную value, которая не объявляется в коде, но подразумевается. Считайте value магической переменной, предоставляемой вам языком программирования С#.
Понятие проблем свойств
Свойства предоставляют внутреннее состояние объекта. Рассматривая приведенный ранее исходный код, вы можете заметить, что переменная-член _exchangeRate имеет прямую связь со свойством ExchangeRate. Если вызывающая сторона присваивает зна¬чение свойству ExchangeRate, то оно немедленно присваивается закрытой переменной-члену exchangeRate. Это предоставляет внутреннее состояние объекта, хоть и косвен¬но. Эта ситуация иллюстрирует, почему многие программисты считают использование свойств плохой практикой программирования.
Предположим, например, что вы хотите разогреть духовку до какой-то температуры, чтобы что-то приготовить. Самый простой способ разогреть духовку — это проконтро¬лировать температуру и создать свойство Temperature примерно так:
class Oven { private int _temperaL.ure; public int Temperature { get {
return _temperature; }
set (
temperature = value; }" ' } 1
Класс Oven предоставляет температуру как свойство, напрямую связанное с пере¬менной temperature. Вызывающая сторона класса Oven (духовка) периодически запра¬шивала бы его о текущей температуре и принимала бы решение о том, достаточно ли она разогрета.
На самом ли деле класс Oven в своей текущей реализации является структурным? Вызывающая сторона класса Oven несет немного ответственности, она должна перио¬дически запрашивать температуру и решать, достаточно ли духовка разогрета. Лучше реализовать класс так, чтобы он мог сам позаботиться о себе. Теперь вызывающей стороне достаточно только спросить: "Духовка уже готова?" Вот так мог бы выглядеть этот код.
class Oven { private int ^temperature;
public void SetTemperature(int temperature) {
^temperature = temperature; } public bool AreYouPreHeated ()
// Проверить, соответствует ли температура духовки необходимой return false;
)
В измененной реализации класса Oven переменная-член _temperature не предостав¬лена. В этой ситуации переменная-член temperature должна действовать как верхний предел температуры, до которого следует разогреть духовку. Верхний предел задается : помощью метода SetTemperature () . Для проверки вы не возвращаете температуру гуховки, а вызываете метод AreYouPreHeated (). Вызывающая сторона получает либо значение true, либо false, указывающее, разогрета ли духовка до готовности.
Вызывающая сторона класса Oven отвечает только за установку предела температу¬ры и выяснение, готова ли уже духовка. Однако вы все еще нуждаетесь в свойствах, по¬скольку класс Oven, в своей текущей форме, представляет собой легкий в использовании класс, который может быть интегрирован в архитектуру на уровне бизнес-логики.
Задача разработчика — навести мост между исходным структурным классом и зада¬чами архитектурного бизнес-класса на логическом уровне. Эта проблема проявится при реализации гостиничного обменного пункта и биржевого валютного брокера. (Помните, а обещал, что мы вернемся ^нашему основному приложению.)
Даже при этих аргументах и различиях между базовыми классами и архитектур¬ными бизнес-классами на логическом уровне некоторые свойства все еще остаются. ГТричина в управлении доступом.
Предположим, что вы стоите в очереди к кассе в гастрономе. Кассир пересчитывает покупки и просит оплатить общий счет. Вы открываете свой бумажник и позволяете кассиру выбрать — кредитную карточку или наличные. Почему бы из тех же соображе-ний вам не передать кассиру весь бумажник? Это вопрос доверия. Вы можете вполне з: зерять кассиру, но лучше все держать под контролем.
Давайте вернемся к примеру с получением доступа к вашему бумажнику, который в упомянул в главе 4, "Структуры данных, решения и циклы". Определенное ранее свой-:тзо Temperature подобно разрешению другим изучать содержимое вашего бумажника. Вообще-то вы вряд ли кому позволили бы сделать это, но если этот человек ваша су-пруга (супруг) или ваша мамочка? Вы отвергли бы и их? Скорей всего, нет, поскольку вы вероятно доверяете своей маме и супруге (супругу). Таким образом, доступ к состоя-аюо — это скорее вопрос доверия и использования правильной области видимости.
-а заметку. В данном обсуждении свойств и объектно-ориентированного проектирования моя задача заключается в том, чтобы объяснить истинное положение дел и изложить обе точки зрения. Не относитесь предвзято ни к тому, ни к другому подходу. Когда вы разрабатываете тип, который скрывает свое состояние, вы создаете тип, реализующий абстрактное намерение. Когда вы разрабатываете тип, который предоставляет свое состояние (до некоторой степени) с помощью свойств, вы реализуете тип, который используется на низком техническом уровне. Имейте также в виду, что иногда внутреннее состояние оказывается внешним состоянием, как, например, в примере с курсом валют. Вы не можете "абстрагироваться" от состояния курса залют, поскольку это значение, используемое в вычислении.