Как правильно писать тесты 46 Цикл разработки 46 Структура проекта с тестами 51 Утверждения (Asserts) 52 Утверждения в форме ограничений 54 Категории 56
Вид материала | Тесты |
СодержаниеНеизменяемые (Immutable) атомарные типы-значения |
- Некорректные задания, 1276.79kb.
- К техническому регламенту, 835.7kb.
- Правительства Российской Федерации от 11 ноября 2005 г. N 679 о порядке разработки, 494.44kb.
- Постановлением Правительства Российской Федерации от 11 ноября 2005 г. N 679 о порядке, 652.85kb.
- Постановлением Правительства Российской Федерации от 11 ноября 2005 г. N 679 о порядке, 623.18kb.
- Правительства Российской Федерации от 11. 11. 2005 N 679 о порядке разработки и утверждения, 533.6kb.
- Постановления Правительства Российской Федерации от 11. 11. 2005 n 679 о порядке разработки, 613.63kb.
- Об утверждении требований к схемам теплоснабжения, порядку их разработки и утверждения, 450.79kb.
- Рабочая программа учебной дисциплины. Общие требования, порядок разработки, согласования, 414.77kb.
- Постановлением Правительства Российской Федерации от 11 ноября 2005 г. N 679 о порядке, 1924.26kb.
Неизменяемые (Immutable) атомарные типы-значения
Суть неизменяемых типов очень проста: после создания они остаются константами. Если параметры, использованные при создании объекта, были правильны, то можно быть уверенным, что объект будет находиться в допустимом (правильном) состоянии. Невозможно изменить внутреннее состояние такого объекта. В итоге сократится число необходимых проверок на ошибки в моменты использования такого объекта. Неизменяемые типы по своей природе являются безопасными в многопоточной среде: к одному объекту может одновременно обращаться любое количество клиентов. Можно без опаски возвращать из объектов переменные неизменяемых типов. Клиент не может случайно изменить внутреннее состояние объектов.
Не каждый тип может быть неизменяемым. Если он является таковым, то для того чтобы все-таки получить измененное значение, потребуется клонировать объекты. Вот почему следующая рекомендация применима и для атомарных, и для неизменяемых типов значений. Разложите типы на структуры, которые составляют единую сущность. Например, это можно сделать для типа Address. Адрес является единым понятием, состоящим из нескольких связанных полей. Изменение одного из полей, очевидно, предполагает изменение и других полей. Тип Customer не является атомарным. Этот тип содержит много различных фрагментов информации: адрес, имя заказчика и один или несколько телефонных номеров. Любой из этих независимых фрагментов информации может подвергнуться изменению. Номер телефона заказчика может измениться, даже если он никуда не переезжает. Заказчик может переехать в другое место, но при этом сохранить прежние номера телефонов. Заказчик может изменить собственное имя, при этом и адрес, и номера телефонов останутся неизменными. Объект Customer не является атомарным; он построен с использованием композиции нескольких неизменяемых типов: адреса, имени и коллекции пар номер телефона/тип номера. Атомарные типы — это единые объекты: их контент должен заменяться полностью. В исключительном случае можно изменить содержимое одного из его компонентов.
Вот типичная реализация адреса, являющаяся изменяемой:
// Mutable Address structure.
public struct Address
{
private string _line1;
private string _line2;
private string _city;
private string _state;
private int _zipCode;
// Rely on the default system-generated
// constructor.
public string Line1
{
get { return _line1; }
set { _line1 = value; }
}
public string Line2
{
get { return _line2; }
set { _line2 = value; }
}
public string City
{
get { return _city; }
set { _city= value; }
}
public string State
{
get { return _state; }
set
{
ValidateState(value);
_state = value;
}
}
public int ZipCode
{
get { return _zipCode; }
set
{
ValidateZip( value );
_zipCode = value;
}
}
// other details omitted.
}
// Example usage:
Address a1 = new Address( );
a1.Line1 = "111 S. Main";
a1.City = "Anytown";
a1.State = "IL";
a1.ZipCode = 61111 ;
// Modify:
a1.City = "Ann Arbor"; // Zip, State invalid now.
a1.ZipCode = 48103; // State still invalid now.
a1.State = "MI"; // Now fine.
Изменение внутреннего состояния означает, что возможна потеря согласованности. После того, как было изменено значение поля City, экземпляр объекта а1 был переведен в неверное состояние. После изменения значения City (город) оно перестало соответствовать значениям полей state (штат) и Zip code (почтовый индекс). Приведенный выше программный код кажется вполне безобидным, но представьте себе, что он является частью многопоточной программы. Любое переключение между потоками после изменения поля City и до того, как будет изменено значение State, может привести к тому, что один из потоков увидит несогласованные данные.
Даже если не брать в расчет (пока!) многопоточную программу, все равно остается вероятность появления неприятностей. Представьте, что индекс оказался неверным, и по этой причине была возбуждена исключительная ситуация. Вы выполнили только часть планировавшихся изменений, после чего система оказалась в противоречивом (несогласованном) состоянии. Для исправления ситуации придется добавить внушительный код проверок. А этот код значительно увеличит размеры и сложность программы в целом. Чтобы полностью защититься от исключительных ситуаций, придется создать страховочные копии во всех местах программы, где изменяется более одного поля. Безопасность при работе с потоками потребует добавления проверок при синхронизации потоков при использовании свойств — и для set, и для get. В конечном счете, все это выльется в значительный по размерам код, который будет, скорее всего, расти в размерах по мере того, как будут добавляться новые возможности.
Вместо этого можно использовать структуру Address неизменяемого типа. Начать следует с того, чтобы сделать все поля экземпляра доступными только для чтения:
public struct Address
{
private readonly string _line1;
private readonly string _line2;
private readonly string _city;
private readonly string _state;
private readonly int _zipCode;
// remaining details elided
}
Кроме того, желательно удалить все части set для каждого из свойств:
public struct Address
{
// ...
public string Line1
{
get { return _line1; }
}
public string Line2
{
get { return _line2; }
}
public string City
{
get { return _city; }
}
public string State
{
get { return _state; }
}
public int ZipCode
{
get { return _zipCode; }
}
}
Теперь у нас имеется неизменяемый тип. Чтобы сделать его полезным, требуется добавить все необходимые конструкторы для полной инициализации структуры Address. Для структуры Address нужен только один конструктор, в котором будет специфицировано каждое поле. Конструктор копирования не требуется, поскольку достаточно эффективным будет и обычный оператор присваивания. Ниже приводится реализация конструктора, в котором все строки оставлены пустыми (null), а значение почтового индекса равно 0:
public struct Address
{
private readonly string _line1;
private readonly string _line2;
private readonly string _city;
private readonly string _state;
private readonly int _zipCode;
public Address( string line1,
string line2,
string city,
string state,
int zipCode)
{
_line1 = line1;
_line2 = line2;
_city = city;
_state = state;
_zipCode = zipCode;
ValidateState( state );
ValidateZip( zipCode );
}
// etc.
}
При использовании неизменяемых типов для модификации их состояния требуется несколько иной подход. Вместо того чтобы модифицировать существующий экземпляр объекта, создается его новый экземпляр:
// Create an address:
Address a1 = new Address( "111 S. Main",
"", "Anytown", "IL", 61111 );
// To change, re-initialize:
a1 = new Address( a1.Line1,
a1.Line2, "Ann Arbor", "MI", 48103 );
Значение al находится в одном из двух состояний: либо в исходном состоянии с размещением в городе Anytown, либо в обновленном состоянии с местонахождением в городе Ann Arbor. Вы не модифицируете существующий адрес, и потому не возникает противоречивых состояний (см. пример выше). Объект будет находиться в неполном состоянии только в течение того времени, пока выполняется конструктор Address, и это состояние невидимо вне конструктора. Как только будет сконструирован (создан с помощью конструктора) новый объект Address, его значение будет зафиксировано раз и навсегда. Оно защищено от исключительных ситуаций: а1 будет иметь либо свое первоначальное, либо новое значение. Если во время создания нового объекта Address будет сгенерировано исключение, а1 останется в исходном значении.
При создании неизменяемого типа следует убедиться в том, что отсутствуют «дыры», позволяющие клиентам изменить внутреннее состояние объекта. Типы-значения не поддерживают производных типов, так что нет необходимости защищаться от модификации полей «при помощи» производных типов. Но нужно наблюдать за любыми полями неизменяемого типа, которые относятся к изменяемым ссылочным типам. При реализации конструкторов для переменных подобных типов необходимо создавать страховочную копию. В наших примерах принимается, что Phone является неизменяемым типом-значением:
// Almost immutable: there are holes that would
// allow state changes.
public struct PhoneList
{
private readonly Phone[] _phones;
public PhoneList( Phone[] ph )
{
_phones = ph;
}
public IEnumerator Phones
{
get
{
return _phones.GetEnumerator();
}
}
}
Phone[] phones = new Phone[10];
// initialize phones
PhoneList pl = new PhoneList( phones );
// Modify the phone list:
// also modifies the internals of the (supposedly)
// immutable object.
phones[5] = Phone.GeneratePhoneNumber( );
Тип массивов (array) является ссылочным типом. Массив, находящийся внутри структуры PhoneList, ссылается на ту же область в памяти (phones), которая была выделена ранее вне объекта и ссылка на которую была передана через параметр конструктора. Клиенты такого класса вполне могут модифицировать вашу «неизменяемую» структуру с помощью другой переменной, ссылающейся на ту же самую область памяти. Для устранения этой возможности необходимо создать страховочную копию массива. Обратите внимание, что в этом примере были показаны «подводные камни» изменяемой коллекции. Еще больше неприятностей может возникнуть, если тип Phone является изменяемым ссылочным типом. Клиенты могут модифицировать значения коллекции, даже если она защищена от любых модификаций. Всякий раз, когда в состав неизменяемого типа включается изменяемый ссылочный тип, следует предусмотреть во всех конструкторах создание страховочной копии:
// Immutable: A copy is made at construction.
public struct PhoneList
{
private readonly Phone[] _phones;
public PhoneList( Phone[] ph )
{
_phones = new Phone[ ph.Length ];
// Copies values because Phone is a value type.
ph.CopyTo( _phones, 0 );
}
public IEnumerator Phones
{
get
{
return _phones.GetEnumerator();
}
}
}
Phone[] phones = new Phone[10];
// initialize phones
PhoneList pl = new PhoneList( phones );
// Modify the phone list:
// Does not modify the copy in pl.
phones[5] = Phone.GeneratePhoneNumber( );
Тем же самым правилам необходимо следовать, когда возвращается изменяемый ссылочный тип.
Степень сложности типа диктует, какую из трех стратегий следует использовать для инициализации неизменяемого типа.
Во-первых, структура Address определяет один конструктор для того, чтобы позволить клиентам инициализировать адрес. Часто наиболее простым подходом является определение разумного набора конструкторов. Помните, что для структур остается доступным и конструктор по умолчанию. В этом структуры отличаются от классов, для которых конструкторы по умолчанию создаются только в том случае, когда нет других конструкторов.
Во-вторых, для инициализации структуры можно создать так называемые фабричные методы (factory methods). Фабрики значительно облегчают создание объектов на основе некоторых общих данных. Тип .NET Framework Color следует этой стратегии при инициализации системных цветов. Статические методы Color.FromKnownColor() и Color.FromName() возвращают копию значения цвета, представляющего текущее значение заданного системного цвета.
В-третьих, можно создать изменяемый класс-помощник для создания экземпляров, в которых необходимо выполнять многошаговые операции для полного создания неизменяемого объекта. Класс строк (string) в .NET следует этому подходу, используя System.Text.StringBuilder. Класс StringBuilder используется для создания строки при помощи нескольких операций. После выполнения всех необходимых операций вы получаете из StringBuilder неизменяемую строку.
Неизменяемые типы проще кодировать и сопровождать. Не следует вслепую реализовывать get и set для каждого свойства. При выборе между struct и class, в котором хранятся данные, на первом месте должны стоять неизменяемые, атомарные типы-значения. Из этих объектов можно с легкостью построить более сложные объекты.
Содержание 1
Процесс разработки программного обеспечения 3
Основные принципы объектно-ориентированного программирования (начало) 12
История 14
Основные принципы объектно-ориентированного программирования (продолжение) 15
Главные понятия 15
Основные принципы 15
Абстракция данных 15
Инкапсуляция 16
Наследование 16
Основные принципы объектно-ориентированного программирования (окончание) 18
Полиморфизм 18
Отношения 19
Основы .NET Framework 22
Введение 22
Обзор выполнения кода в среде CLR 26
Компиляция исходного кода в управляемые модули 26
Части управляемого модуля 28
Объединение управляемых модулей в сборку 29
Загрузка CLR при выполнении программы 31
Исполнение кода сборки 31
IL и верификация 36
Небезопасный код 36
IL и защита интеллектуальной собственности 37
NGen.exe — генератор объектного кода 38
Библиотека классов .NET Framework 38
Общая система типов (Common Type System, CTS) 40
Общеязыковая спецификация 42
Модульное тестирование (unit testing) 44
Предпосылки 44
Преимущества 44
Поощрение изменений 45
Упрощение интеграции 45
Документирование кода 45
Отделение интерфейса от реализации 45
Ограничения 46
Как правильно писать тесты 46
Цикл разработки 46
Структура проекта с тестами 51
Утверждения (Asserts) 52
Утверждения в форме ограничений 54
Категории 56
Настройка среды выполнения тестов 57
Дополнительные утверждения 59
Тесты и исключения 60
Правила тестирования 62
Юнит-тестирование 70
Пример 70
Два вида констант 98
Операторы is, as и приведение типов 101
Метод ToString() 109
Типы-значения и ссылочные типы 117
Неизменяемые (Immutable) атомарные типы-значения 122
0 в типах-значениях 132
Методы ReferenceEquals(), Equals(), статический метод Equals() и operator== 134
Циклы foreach 140
Управление ресурсами в .NET 143
Инициализаторы переменных 149
Инициализация статических полей классов с помощью статических конструкторов 155
Цепочки конструкторов 157
Применение операторов using и try/finally для освобождения ресурсов 160
О минимизации мусора 167
Упаковка и распаковка 169
Наследование классов и реализация интерфейсов 171
Отличие реализации методов интерфейса от переопределения виртуальных методов 178
Введение в паттерны проектирования 184
Что такое паттерн проектирования 186
Паттерны проектирования в схеме MVC 188
Описание паттернов проектирования 190
Каталог паттернов проектирования 192
Как решать задачи проектирования с помощью паттернов 195
Механизмы повторного использования 204
Сравнение структур времени выполнения и времени компиляции 210
Проектирование с учетом будущих изменений 212
Как выбирать паттерн проектирования 220
Как пользоваться паттерном проектирования 221
Делегаты и события 226
Делегаты 226
События 230
Параметры событий 233
Атрибуты 237
Синтаксис 237
Создание атрибута 240
Составляющие класса атрибута 242
Получение значений атрибута 244
Задание 1 245
Задание 2 246
Обобщения 251
Проблемы создания объектных образов и восстановления значений 252
Типовая безопасность и строго типизованные коллекции 253
Проблемы создания объектных образов и строго типизованные коллекции 258
Пространство имен System.Collections.Generic 259
Тип List
Создание обобщенных методов 263
Пропуск параметров типа 265
Создание обобщенных структур (и классов) 267
Ключевое слово default в обобщенном программном коде 269
Создание пользовательских обобщенных коллекций 271
Установка ограничений для параметров типа с помощью where 274
Отсутствие поддержки ограничений при использовании операций 279
Создание обобщенных базовых классов 280
Создание обобщенных интерфейсов 282
Создание обобщенных делегатов 284
Несколько слов о вложенных делегатах 286
Задачи 286
Примеры реализации по шаблонам Мост+Фабрика 289
Пример 1 289
Пример 2 292
Пример 3. Контроллер в виде интерфейса, а не класса. 294
Различное поведение фабрик 298
Фабрики для создания плагинов 299
Фабрики для создания объектов по некоторому алгоритму 300
Фабрики для клонирования объектов 300
Некоторые замечания об использовании свойств и наследования 302
Замечания об использовании свойств 302
Замечания об использовании наследования. Проблема «хрупкого» базового класса. 304