Паттерны проектирования на платформе .NET

Паттерны проектирования на платформе .NET
Автор: Сергей Тепляков
Год: 2015
ISBN: 978-5-496-01649-0
Страниц: 320
Язык: Русский
Формат: PDF
Размер: 10 Мб

Download

Паттерны проектирования остаются важным инструментом в арсенале разработчика, поскольку они опираются на фундаментальные принципы проектирования. Тем не менее, появление новых конструкций в современных языках программирования делает одни паттерны более важными, а значимость других сводит к минимуму.
Цель данной книги – показать, как изменились паттерны проектирования за это время, как на них повлияло современное увлечение функциональным программированием, и объяснить, каким образом они используются в современных .NET-приложениях. В издании вы найдете подробное описание классических паттернов проектирования с особенностями их реализации на платформе .NET, а также примеры их использования в .NET Framework. Вы также изучите принципы проектирования, известные под аббревиатурой SOLID, и научитесь применять их при разработке собственных приложений.
Книга предназначена для профессиональных программистов, которые хотят изучить особенности классических принципов и паттернов программирования с примерами на языке C# и понять их роль в разработке современных приложений на платформе .NET.

+

Паттерны поведения

«Стратегия» (Strategy)
«Шаблонный метод» (Template Method)
‰«Посредник» (Mediator)
‰«Итератор» (Iterator)
‰«Наблюдатель» (Observer)
«Посетитель» (Visitor)
Другие паттерны поведения

Порождающие паттерны

«Синглтон» (Singleton)
«Абстрактная фабрика» (Abstract Factory)
‰«Фабричный метод» (Factory Method)
«Строитель» (Builder)

Паттерн «Синглтон» (Singleton)

Назначение: гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему.
Другими словами: синглтон эмулирует глобальные переменные в объектно-ориентированных языках программирования.
Мотивация
Практически в любом приложении возникает необходимость в глобальных переменных или объектах с ограниченным числом экземпляров. Даже в таком простом приложении, как импорт логов, может возникнуть необходимость в логировании. И самый простой способ решить эту задачу — создать глобальный объект, который будет доступен из любой точки приложения.

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

Варианты реализации в .NET

В оригинальном описании паттерна «Синглтон» «бандой четырех» на его реализацию не накладывались никакие ограничения, однако на практике (в частности, для платформы .NET) любая реализация должна отвечать двум требованиям:

  • ‰в многопоточной среде должна обеспечиваться возможность доступа к синглтону;
  • ‰должна обеспечиваться «ленивость» создания синглтона.

Потокобезопасность является необходимым свойством, поскольку представить себе реальное однопоточное .NET-приложение довольно сложно. «Ленивость» же является скорее желательным свойством реализации.

Стратегия vs. шаблонный метод

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

Паттерн освобождения ресурсов (Dispose Pattern)

На платформе .NET проблемой управления памятью занимается сборщик мусо­ра, но при этом остаются вопросы со своевременной очисткой ресурсов. В языке
С++ для этого используются деструкторы, а в управляемых языках, таких как C#, — метод Dispose интерфейса IDisposable, который и дал название соответствующему паттерну проектирования.

Паттерн освобождения ресурсов (Dispose Pattern) на платформе .NET весьма тяжеловесен. Почему вообще появилась необходимость в управлении ресурсами в системе с автоматической сборкой мусора? Потому что, помимо памяти, прило­жение может владеть и другими важными ресурсами, такими как дескрипторы
файлов и потоков, мьютексы, семафоры и т. п. Время сборки мусора зависит от многих факторов и не может (и не должно) контролироваться приложением. С ре­сурсами дело обстоит иначе: разработчик должен приложить все усилия, чтобы время владения ими было минимальным.

В основе паттерна освобождения ресурсов лежат понятия управляемых и не­управляемых ресурсов, которые в рамках «управляемой» платформы кажутся нело­гичными. Неуправляемые ресурсы представляют собой объекты, о которых совер­шенно ничего не известно среде исполнения (CLR, Common Language Runtime). Хорошим примером могут служить «сырые дескрипторы», представленные значе­нием IntPtr. Как только «сырой объект» оборачивается в управляемый класс, такой ресурс становится управляемым.

Управление ресурсами в .NET основывается на интерфейсе IDisposable, метод Dispose которого вызывается пользовательским кодом, и на финализаторе (finalizers), который вызывается во время сборки мусора. Разница между финали­затором и методом Disposeсостоит в том, что первый вызывается сборщиком мусора в неизвестный момент времени, при этом порядок вызова финализаторов для разных объектов не определен. Второй вызывается пользовательским кодом, после чего ссылка на «мертвый» объект продолжает существовать.

Полноценная реализация паттерна управления ресурсами для «незапечатанных» (non­sealed) классов довольно тяжеловесна. Разработчик класса должен добавить дополнительный виртуальный метод Dispose(bool), ответственный за освобо­ждение управляемых и неуправляемых ресурсов в зависимости от переданного аргумента (листинг 2.8).

Структурные паттерны

«Адаптер» (Adapter)
«Фасад» (Facade)
«Декоратор» (Decorator)
«Компоновщик» (Composite)
«Заместитель» (Proxy)

Языковые адаптеры

Компилятор языка C# налагает определенные ограничения на применение пользовательских типов в некоторых языковых конструкциях. Например, для использования типа в LINQ-выражениях вида fromsinobj требуются экземплярные методы Select, Where, OrderBy и др., но подойдут и методы расширения. Для использования в цикле foreach типу необходимо предоставить метод GetEnumerator, который возвращает тип с методом boolMoveNext и свойством Current. Для применения async/await требуется метод Get Awaiter и GetResult. Для использования инициализатора коллекций требуется реализация интерфейса IEnumerable<T>(или IEnumerable) и метода Add.

Вместо того чтобы модифицировать код приложения так, чтобы он удовлетворял этим требованиям, можно создать набор классов-адаптеров или методов расширения. Начиная с 6-й версии языка C#, для использования инициализатора коллекций достаточно иметь метод расширения Add(листинг 12.3).

Инкапсуляция стороннего кода

Использование фасадов не только упрощает использование библиотек или сторонних компонентов, но и решает ряд насущных проблем.

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

Повышение уровня абстракции

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

Принципы проектирования

‰Принцип единственной обязанности
Принцип «открыт/закрыт»
‰Принцип подстановки Лисков
Принцип разделения интерфейсов
Принцип инверсии зависимостей
‰Размышления о принципах проектирования

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

В этой части мы рассмотрим популярные принципы проектирования, обозначенные аббревиатурой SOLID. В середине 1990-х годов Роберт Мартин начал публиковать в журнале C++ Report статьи на тему проектирования. В качестве основы было взято несколько известных ранее принципов проектирования, добавлены собственные мысли, и на свет появились фундаментальные принципы объектно-ориентированного проектирования.

Изначально эти принципы были описаны в несколько ином порядке и в архиве «дядюшки Боба» значатся под аббревиатурой SOLID. Через несколько лет они
перекочевали в книгу Agile Software Development, Principles, Patterns, and Practices, а со временем — в аналогичную книгу с примерами на языке C# «Принципы, практики и методики гибкой разработки на языке C#». Здесь порядок уже был иным и появились «цельные» (SOLID) принципы, звучность названия которых в немалой степени обеспечила им успех.

Оригинальные статьи были опубликованы почти 20 лет назад, когда объектно-ориентированное мышление только становилось мейнстримом, в качестве примеров использовался язык С++. Столь почтенный возраст дает о себе знать, и оригинальные статьи Роберта Мартина содержат ряд советов, полезных лишь
для языка С++ двадцатилетней давности. Основной упор делается на критике структурного программирования и восхвалении объектно-ориентированного
подхода. Любопытно, что многие «современные» описания SOLID-принципов все еще показывают пользу полиморфизма на примере иерархии фигур и говорят о проблемах транзитивных зависимостей, которые далеко не столь актуальны в C# или Java.

Я же хочу рассмотреть SOLID-принципы с абстрактной точки зрения, подумать о том, какую проблему они призваны решать (и решают ли) и как мы должны
смотреть на них сегодня, когда ООП уже давно стало широко распространенной парадигмой программирования.

Размышления о принципах проектирования

Для чего понадобилось кому-то выдумывать разные паттерны проектирования, принципы и методики разработки? Разве не было бы проще научить разработчиков хорошему проектированию? Или почему тогда не формализовать процесс разработки и не ввести четкие количественные метрики дизайна, которые бы говорили о качестве решения?

Правильное использование принципов проектирования

Применение любого принципа проектирования имеет свою цену. Дробление класса на более мелкие составляющие, чтобы он отвечал принципу единственной обязанности, может привести к «размазыванию» логики по нескольким классам. А это может привести к уменьшению связности (lowcohesion) и читабельности, а иногда и к падению производительности.

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