Студопедия — ПОСТРОЕНИЕ ПРОГРАММНЫХ ПРИЛОЖЕНИЙ
Студопедия Главная Случайная страница Обратная связь

Разделы: Автомобили Астрономия Биология География Дом и сад Другие языки Другое Информатика История Культура Литература Логика Математика Медицина Металлургия Механика Образование Охрана труда Педагогика Политика Право Психология Религия Риторика Социология Спорт Строительство Технология Туризм Физика Философия Финансы Химия Черчение Экология Экономика Электроника

ПОСТРОЕНИЕ ПРОГРАММНЫХ ПРИЛОЖЕНИЙ






 

А.А. Волосевич

 

ЯЗЫК C# И ПЛАТФОРМА.NET

(часть 3)

 

Курс лекций

для студентов специальности I-31 03 04 «Информатика»

всех форм обучения

 

 

Минск 2009


СОДЕРЖАНИЕ

3. ПОСТРОЕНИЕ ПРОГРАММНЫХ ПРИЛОЖЕНИЙ.. 4

3.1. МОДУЛЬНОЕ ТЕСТИРОВАНИЕ.. 4

3.2. ШАБЛОНЫ ПРОЕКТИРОВАНИЯ.. 10

3.3. СТРУКТУРНЫЕ ШАБЛОНЫ: Декоратор, Заместитель, мост.. 11

Декоратор (Decorator) 11

Заместитель (Proxy) 13

Мост (Bridge) 15

3.4. СТРУКТУРНЫЕ ШАБЛОНЫ: КОМПОНОВЩИК И ПРИСПОСОБЛЕНЕЦ.. 17

Компоновщик (Composite) 17

Приспособленец (Flyweight) 19

3.5. СТРУКТУРНЫЕ ШАБЛОНЫ: АДАПТЕР И ФАСАД.. 22

Адаптер (Adapter) 22

Фасад (Façade) 23

3.6. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: ПРОТОТИП, ФАБРИЧНЫЙ МЕТОД, ОДИНОЧКА.. 24

Прототип (Prototype) 24

Фабричный метод (Factory method) 26

Одиночка (Singleton) 27

3.7. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: АБСТРАКТНАЯ ФАБРИКА И СТРОИТЕЛЬ.. 29

Абстрактная фабрика (Аbstract factory) 29

Строитель (Builder) 32

3.8. ШАБЛОНЫ ПОВЕДЕНИЯ: СТРАТЕГИЯ, СОСТОЯНИЕ, ШАБЛОННЫЙ МЕТОД.. 35

Стратегия (Strategy) 35

Состояние (State) 37

Шаблонный метод (Template method) 41

3.9. ШАБЛОНЫ ПОВЕДЕНИЯ: ЦЕПОЧКА ОБЯЗАННОСТЕЙ И КОМАНДА.. 43

Цепочка обязанностей (Chain of responsibility) 43

Команда (Command) 44

3.10. ШАБЛОНЫ ПОВЕДЕНИЯ: ИТЕРАТОР, ПОСРЕДНИК, НАБЛЮДАТЕЛЬ.. 48

Итератор (Iterator) 48

Посредник (Mediator) 50

Наблюдатель (Observer) 53

3.11. ШАБЛОНЫ ПОВЕДЕНИЯ: ПОСЕТИТЕЛЬ, ИНТЕРПРЕТАТОР, ХРАНИТЕЛЬ.. 55

Посетитель (Visitor) 55

Интерпретатор (Interpreter) 60

Хранитель (Memento) 64

3.12. НЕКОТОРЫЕ НЕКЛАССИЧЕСКИЕ ШАБЛОНЫ ПРОЕКТИРОВАНИЯ.. 67

Неизменный объект (Immutable object) 67

Пул объектов (Object pool) 68

Отложенная инициализация (Lazy initialization) 68

Нулевой объект (Null object) 69

3.13. АНТИПАТТЕРНЫ... 71

3.14. АРХИТЕКТУРА ПРОГРаммного Обеспечения.. 74

«Клиент-сервер». 75

Архитектура, основанная на использовании компонентов. 76

Многоуровневая архитектура. 77

Шина сообщений.. 80

Выделенное представление. 81

N-звеньевая архитектура. 81

Объектно-ориентированная архитектура. 82

Архитектура, ориентированная на сервисы.. 84


ПОСТРОЕНИЕ ПРОГРАММНЫХ ПРИЛОЖЕНИЙ

3.1. МОДУЛЬНОЕ ТЕСТИРОВАНИЕ

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

Модульное тестирование (unit testing) ‑ процесс в программировании, позволяющий проверить на корректность отдельные модули исходного кода программы. При проведении модульного тестирования для каждой нетривиальной функции модуля создаются тесты (так называемые юнит-тесты), проверяющие правильность работы функции. Затем набор тестов запускается (как правило, в автоматическом режиме), и анализируется результат тестового прогона. Кроме выявления ошибок, прогон тестов позволяет достаточно быстро проверить, не привело ли изменение кода к регрессии, то есть к появлению ошибок в уже написанных и оттестированных функциях. Правильно составленные юнит-тесты также способны работать как «живой документ» для модуля: клиенты, которые не знают, как использовать данный модуль, могут использовать юнит-тест в качестве примера.

Когда модульное тестирование используется совместно с объектно-ориентированным проектированием, термин «модуль» соответсвует понятию «класс», а термин «функция модуля» ‑ понятию «метод класса». Для большинства современных платформ программирования существуют специальные библиотеки модульного тестирования. Широко распространенной библиотекой тестирования для платформы.NET является NUnit. С сайта http://www.nunit.org можно скачать саму библиотеку, документацию, а также графическую оболочку для запуска тестов.

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

using System;

using System.Collections.Generic;

 

namespace Lists

{

public class IntegerList

{

private readonly List<int> data;

 

public int Count { get { return data.Count; } }

 

public int this[int index]

{

get { return data[index]; }

set { data[index] = value; }

}

 

public IntegerList(int count)

{

if (count < 0)

{

throw new ArgumentException("Count must be >= 0");

}

data = new List<int>(count);

}

 

public void Add(int element)

{

data.Add(element);

}

 

public int FindMax()

{

var max = Int32.MinValue;

foreach (var element in data)

{

if (element > max)

{

max = element;

}

}

return max;

}

}

}

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

Таблица 1

Соответствия между тестируемым и тестирующим кодами

Объект тестирования Объект модульного теста
Проект Создайте проект (библиотеку классов), содержащий тесты, с именем ИсходныйПроект. Tests
Класс Для каждого класса, подлежащего тестированию, создайте, по крайней мере, один тестирующий класс с именем ИмяКласса Tests. Такие тестирующие классы называются наборами тестов (test fixtures)
Метод Для каждого метода, подлежащего тестированию, создайте, по крайней мере, один тестирующий public-метод (тест) с именем ИмяМетода_ТестируемоеУсловие_ОжидаемоеПоведение
Следуя правилам табл. 1, создадим проект с именем Lists.Tests, содержащий ссылку на библиотеку nunit.framework.dll. Поместим в этот проект класс IntegerListTests с методом Count_AddTwoElements_Return2().

using NUnit.Framework;

using Lists;

 

namespace Lists.Tests

{

[TestFixture]

public class IntegerListTests

{

[Test]

public void Count_AddTwoElements_Return2()

{

var obj = new IntegerList(2);

obj.Add(3);

obj.Add(-1);

Assert.AreEqual(2, obj.Count);

}

}

}

Как видно из приведённого листинга, класс, который является набором тестов, помечается специальным атрибутом [TestFixture], а отдельный тест – атрибутом [Test]. Ниже будут рассмотрены и другие атрибуты библиотеки NUnit. Структура метода-теста достаточно типична. Вначале производится создание и настройка необходимых объектов. Затем выполняются запланированные действия с объектами. В конце формулируется некое утверждение, которое будет истинно, если тест закончился правильно. Формулировка утверждения производится при помощи методов класса Assert, который далее будет рассмотрен подробно.

Тестовые проекты компилируются как обычные проекты в составе решения. Для запуска и анализа тестов NUnit предлагает простую графическую оболочку[1]. При работе с этой оболочкой указывается скомпилированная сборка с тестами. Затем NUnit (используя механизм отражения) отыскивает и самостоятельно запускает все тесты из наборов, отображая отчет о тестовом прогоне.

Рис. 1. Окно графической оболочки NUnit.

Ознакомившись с основными шагами процесса модульного тестирования, рассмотрим возможности библиотеки NUnit подробнее. Для настройки тестов NUnit преоставляет несколько атрибутов, два из которых уже были рассмотрены: [TestFixture] и [Test]. Класс, содержащий тесты, может иметь методы, помеченные атрибутами [SetUp] и [TearDown]. Если метод помечен атрибутом [SetUp], то он выполняется перед каждым тестовым методом. Атрибут [TearDown] используется для методов, выполняемых после каждого теста. Обычно методы с атрибутами [SetUp] и [TearDown] – это методы подготовки и завершения отдельного теста. Атрибуты [TestFixtureSetUp] и [TestFixtureTearDown] также применяются к методам, но служат для подготовки и завершения всего тестового набора.

[TestFixture]

public class IntegerListTests

{

private IntegerList lst = null;

 

[SetUp]

public void Prepare()

{

lst = new IntegerList(10);

}

 

[TearDown]

public void Release()

{

lst = null;

}

 

[Test]

public void Count_AddTwoElements_Return2()

{

lst.Add(3);

lst.Add(-1);

Assert.AreEqual(2, lst.Count);

}

}

Нередко тест выполняется для того, чтобы проверить, генерирует ли метод требуемое исключение. Такой тест можно пометить атрибутом [ExpectedException] с указанием типа ожидаемой исключительной ситуации. Кроме этого, атрибут [ExpectedException] допускает дополнительное указание ожидаемого сообщения при генерации исключения. Метод с атрибутом [ExpectedException] обычно не содержит обращений к классу Assert.

[Test]

[ExpectedException(typeof(ArgumentException))]

public void Constructor_CallWithNegativeArg_ThrowException()

{

var obj = new IntegerList(-1);

}

Атрибут [Ignore] разрешает проигнорировать тест при прогоне, а атрибут [Category] позволяет указать произвольную категорию для теста.

Практически любой тест использует один или несколько статических методов класса Assert для формулировки тестового утверждения. Упомянем такие методы этого класса, как AreEqual(), Less(), Greater(), GreaterOrEqual(), LessOrEqual(), IsNull(), IsNotNull(), AreSame(), IsTrue(). Начиная с версии NUnit 2.4 доступен новый способ использованием Assert: применяется метод Assert.That(), и формулировка утверждения записывается как аргумент этого метода.

// два эквивалентных утверждения

// в старом

Assert.AreEqual(actual, expected);

// и новом синтаксисе

Assert.That(actual, Is.EqualTo(expected));

Ознакомившись с техникой тестирования, рассмотрим некоторые вопросы тестовой методологии. Специалисты модульного тестирования выделяют шесть областей, на которые распадается общий набор юнит-тестов. Эти области известны в виде акронима Right-BICEP.

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

· B oundary Condition. Данные тесты проверяют работу модуля на различных граничных условиях. Возможные граничные условия формируют акроним CORRECT:

o C onformance – входные и выходные данные метода удовлетворяют (conform) определенному ожидаемому формату.

o O rdering ‑ исходные данные находятся в упорядоченном или неупорядоченном множестве.

o R ange ‑ проверка попадания исходных данных в разумный диапазон.

o R eference ‑ граничные условия, предполагающие, что до или после тестируемого метода выполняются некие действия, т. е. тестируемый метод имеет ссылки на другие методы.

o E xistence ‑ проверка того, что данные существуют (не равны нулю, не равны null, и т. п.).

o C ardinality ‑ проверка того, что исходные данные поступают на вход в нужном количестве.

o T ime ‑ проверка порядка обработки данных.

· I nverse Relationship. Тесты из области обращения отношения пытаются обратить работу тестируемого метода, чтобы по результату метода получить его входные данные. Типичный пример: для тестирования метода извлечения квадратного корня результат работы этого метода возводится в квадрат и сравнивается с аргументом метода.

· C ross-check. Тест из области перепроверки реализует альтернативную стратегию работы проверяемого метода. Например, если тестируется метод, обрабатывающий массив по алгоритму быстрой сортировки, в тесте используется алгоритм сортировки вставками, чтобы сравнить полученный результат и результат тестируемого метода.

· E rror. Данные тесты создают условия, приводящие к генерированию в тестируемом коде различных исключительных ситуаций. Часто рассматриваются такие исключительные ситуации, как нехватка памяти, нехватка дискового пространства, сетевые ошибки, ошибки прав доступа к объектам операционной системы и т.п.

· P erformance Characteristics. Тесты из области характеристики производительности замеряют время выполнения кода, а также то, насколько изменяется время выполнения при изменении размера входных данных.

Так как модульное тестирование предполагает тестирование изолированных компонент, достаточно часто возникает необходимость в моделировании контекста для тестируемого объекта. При этом обычно применяются объекты-заглушки (stubs) и поддельные объекты (mock objects). Хотя разница между этими понятиями, в принципе, условна, адепты модульного тестирования поясняют её так. Объект-заглушка используется тестируемым объектом, а поддельный объект тестируется сам, но работает при этом с тестируемым объектом.

Рис. 2. Объект-заглушка и поддельный объект.

Для упрощения создания объектов-заглушек и поддельных объектов существуют специальные библиотеки и платформы (см., например, Rhino Mocks).

3.2. ШАБЛОНЫ ПРОЕКТИРОВАНИЯ

Шаблоны (или паттерны) проектирования (design patterns) – это многократно используемые решения распространенных проблем, возникающих при разработке программного обеспечения[2]. По мере накопления личного опыта и профессионального роста программист обычно замечает сходство новых проблем проектирования с решёнными им ранее. В дальнейшем приходит осознание того, что решения похожих проблем представляют собой повторяющиеся шаблоны. Зная эти шаблоны, опытные программисты распознают ситуацию их применения и сразу используют готовое решение, не тратя времени на предварительный анализ проблемы.

Историю шаблонов проектирования принято начинать с книг архитектора Кристофера Александера (Christopher Alexander). В семидесятых годах двадцатого века он составил набор шаблонов для применения в строительной архитектуре и городском планировании. Однако в области архитектуры эта идея не получила такого развития, как позже в области программной разработки.

В 1987 году Кент Бэк (Kent Beck) и Уард Каннигем (Ward Cunningham) использовали некоторые идеи Александера и разработали шаблоны на языке Smalltalk для программ пользовательского интерфейса. В 1988 году Эрих Гамма (Erich Gamma) начал писать докторскую работу при цюрихском университете об общей переносимости методики Александера на разработку программ. В 1991 году Гамма заканчивает свою диссертацию и в сотрудничестве с Ричардом Хелмом (Richard Helm), Ральфом Джонсоном (Ralph Johnson) и Джоном Влиссидсом (John Vlissides) публикует книгу «Design Patterns ‑ Elements of Reusable Object-Oriented Software»[3]. В книге были описаны 23 шаблона проектирования. «Design Patterns» стала причиной роста популярности шаблонов, а сами шаблоны из книги скоро начали называть классическими. В дальнейшем список шаблонов пополнялся и адаптировались для различных языков программирования.

Рассмотрим аргументы «за» и «против» применения шаблонов проектирования. Главная польза каждого отдельного шаблона состоит в том, что он описывает решение целого класса абстрактных проблем. Тот факт, что каждый шаблон имеет свое имя, облегчает дискуссию между разработчиками. Таким образом, за счёт шаблонов производится унификация терминологии, названий модулей и элементов проекта. Однако есть мнение, что слепое применение шаблонов из справочника, без осмысления причин и предпосылок выделения шаблона, замедляет профессиональный рост программиста, так как подменяет творческую работу механической подстановкой. Люди, придерживающиеся данного мнения, считают, что знакомиться со списками шаблонов следует тогда, когда программист «дорос» до них в профессиональном плане. Хороший критерий нужной степени профессионализма ‑ выделение шаблонов самостоятельно, на основании собственного опыта. При этом, разумеется, знакомство с теорией, связанной с шаблонами, полезно на любом уровне профессионализма и направляет развитие программиста в правильную сторону.

Существует несколько способов группировки шаблонов. Ниже представлена классификация шаблонов в зависимости от назначения.

· Структурные шаблоны – показывают, как объекты и классы объединяются для образования сложных структур.

· Порождающие шаблоны – управляют и контролируют процесс создания и жизненный цикл объектов.

· Шаблоны поведения – используются для организации, управления и объединения различных вариантов поведения объектов.

3.3. СТРУКТУРНЫЕ ШАБЛОНЫ: Декоратор, Заместитель, мост

Декоратор (Decorator)

Шаблон Декоратор даёт способ для динамического добавления к объекту нового состояния и поведения. При этом исходный объект не знает о том, что он «декорируется». Ключевым моментом реализации шаблона является то, что класс-декоратор одновременно наследуется от декорируемого класса и агрегирует объект этого класса.

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

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

Рис. 3. UML-диаграмма шаблона Декоратор.

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

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

using System.Drawing;

 

public class Photo

{

public Image Image { get; set; }

public string Caption { get; set; }

 

public Photo()

{

Image = new Bitmap(100, 100);

}

 

public virtual void Draw(Graphics canvas)

{

canvas.DrawImage(Image, 0, 0);

}

}

 

public class TaggedPhoto: Photo

{

private readonly Photo photo;

private readonly string tag;

 

public TaggedPhoto(Photo source, string tag)

{

photo = source;

this.tag = tag;

}

 

public override void Draw(Graphics canvas)

{

photo.Draw(canvas);

canvas.DrawString(tag, new Font("Arial", 16),

new SolidBrush(Color.Black),

new PointF(20, 20));

}

}

 

public class DecoratorExample

{

private static void Main()

{

var photo = new Photo();

var taggedPhoto = new TaggedPhoto(photo, "the first tag");

var doubleTagged = new TaggedPhoto(taggedPhoto,

"the second tag");

}

}

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

Заместитель (Proxy)

Шаблон проектирования Заместитель применяется для контроля создания и доступа к объекту. Заместитель – это обычно небольшой открытый объект, «за спиной» которого находится сложный объект, выполняющий реальную работу и создающийся только при наступлении определённых условий.

Иллюстрацией шаблона Заместитель может служить работа с сайтом, который требует необязательной авторизации. Если пользователь не авторизовался, ему доступен сравнительно небольшой функционал. После авторизации (это условие создания сложного объекта) активируются дополнительные функции.

На рис 4. показана UML-диаграмма дизайна шаблона Заместитель. Для унификации доступа клиента к различным заместителям предполагается (хотя это и не обязательно), что все заместители реализуют общий интерфейс. Заместитель агрегирует сложный объект, контролируя доступ к нему. Клиент не обязательно имеет прямой доступ к замещаемому объекту.

Рис. 4. UML-диаграмма шаблона Заместитель.

Следующий пример кода показывает использование шаблона Заместитель. Первый заместитель управляет созданием замещаемого объекта – объект создаётся один раз при первой необходимости (такая разновидность шаблона носит название виртуальный заместитель). Второй заместитель показывает применение идей авторизованного доступа к функциям.

public interface ISubject

{

string Request();

}

 

internal class Subject

{

public string Request()

{

return "Subject Request";

}

}

 

// Виртуальный заместитель - создает реальный объект

// только при первом вызове его метода

public class VirtualProxy: ISubject

{

private Subject subject;

 

public string Request()

{

if (subject == null)

subject = new Subject();

return "Proxy: Call to " + subject.Request();

}

}

 

// Аутентификационный заместитель - для доступа нужен пароль

public class ProtectionProxy: ISubject

{

private Subject subject;

private const string Password = "Abracadabra";

 

public string Authenticate(string supplied)

{

if (supplied!= Password)

return "Protection Proxy: No access";

subject = new Subject();

return "Protection Proxy: Authenticated";

}

 

public string Request()

{

if (subject == null)

{

return "Protection Proxy: Authenticate first";

}

return "Protection Proxy: Call to " + subject.Request();

}

}

Мост (Bridge)

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

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

Рис. 5 демонстрирует упрощенную диаграмму дизайна шаблона Мост. На диаграмме показан только один абстрактный тип. Этот тип агрегирует некий конкретный объект-мост, которому делегируется выполнение требуемых операций.

Рис. 5. UML-диаграмма шаблона Мост.

При реализации шаблона инстанциирование конкретного моста обычно происходит в конструкторе. Далее приведён пример кода, приблизительно соответствующий описанной выше иллюстрации применения шаблона Мост.

public class Sensor

{

private readonly Bridge bridge;

 

public Sensor(Bridge bridge)

{

this.bridge = bridge;

}

 

public double GetValue()

{

return bridge.GetSensorValue();

}

}

 

public interface Bridge

{

double GetSensorValue();

}

 

public class ImplementationA: Bridge

{

public double GetSensorValue()

{

System.Console.WriteLine("Implementation A");

return -1;

}

}

 

public class ImplementationB: Bridge

{

public double GetSensorValue()

{

System.Console.WriteLine("Implementation B");

return 0;

}

}

 

public class BridgeExample

{

private static void Main()

{

var sensor = new Sensor(new ImplementationA());

sensor.GetValue();

}

}

3.4. СТРУКТУРНЫЕ ШАБЛОНЫ: КОМПОНОВЩИК И ПРИСПОСОБЛЕНЕЦ

Компоновщик (Composite)

Компоновщик – один из простейших структурных шаблонов. Его задача – агрегировать объекты, обладающие общим интерфейсом, и обеспечить выполнение операций этого интерфейса над всеми агрегируемыми объектами.

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

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

Рис. 6. Дизайн шаблона Компоновщик.

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

using System;

using System.Collections.Generic;

 

public interface IComponent<T>

{

T Item { get; set; }

void Add(IComponent<T> c);

IComponent<T> Remove(T s);

IComponent<T> Find(T s);

}

 

// Отдельный компонент

public class Component<T>: IComponent<T>

{

public T Item { get; set; }

 

public Component(T item)

{

Item = item;

}

 

public void Add(IComponent<T> c)

{

Console.WriteLine("Cannot add to an item");

}

 

public IComponent<T> Remove(T s)

{

Console.WriteLine("Cannot remove directly");

return this;

}

 

public IComponent<T> Find(T s)

{

return s.Equals(Item)? this: null;

}

}

 

// Компоновщик

public class Composite<T>: IComponent<T>

{

private readonly List<IComponent<T>> list;

 

public T Item { get; set; }

 

public Composite(T item)

{

Item = item;

list = new List<IComponent<T>>();

}

 

public void Add(IComponent<T> c)

{

list.Add(c);

}

 

public IComponent<T> Remove(T s)

{

var p = Find(s);

if (p!= null)

{

list.Remove(p);

}

return this;

}

 

public IComponent<T> Find(T s)

{

if (Item.Equals(s))

{

return this;

}

IComponent<T> found = null;

foreach (var c in list)

{

found = c.Find(s);

if (found!= null)

{

break;

}

}

return found;

}

}

Приспособленец (Flyweight)

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

Рассмотрим диаграмму шаблона Приспособленец (рис. 7).

Рис. 7. Диаграмма шаблона Приспособленец.

Суть шаблона в том, чтобы разделить состояние некоторого объекта на состояния трех типов. Внутреннее состояние (intrinsic state) принадлежит самому объекту. Тип Flyweight реализует интерфейс IFlyweight, который определяет операции, в которых заинтересована остальная часть системы. Клиент владеет общим (неразделяемым) состоянием (unshared state), а также коллекцией приспособленцев, которых производит класс-фабрика (FlyweightFactory). Наконец, внешнее состояние (extrinsic state) не появляется в системе как таковое. Если оно понадобится, то будет вычислено уже во время выполнения программы для каждого внутреннего состояния.

Отметим некоторые нюансы реализации шаблона Приспособленец. Тип Flyweight может быть реализован в виде структуры – он небольшой и ни от чего не наследует. Фабрика приспособленцев создает объекты в соответствии со специальными требованиями: нужно проверить, существует ли объект, и если не существует, то создать его и добавить во внутреннюю коллекцию-словарь.

using System;

using System.Collections.Generic;

 

public class CharacterFactory

{

private readonly Dictionary<char, Character> chars =

new Dictionary<char, Character>();

 

public Character GetCharacter(char key)

{

if (!chars.ContainsKey(key))

{

switch (key)

{

case 'A':

chars.Add(key, new CharacterA());

break;

case 'B':

chars.Add(key, new CharacterB());

break;

}

}

return chars[key];

}

}

 

public abstract class Character

{

public char Symbol { get; set; }

public int Width { get; set; }

public int Height { get; set; }

public int PointSize { get; set; }

public abstract void Display(int pointSize);

}

 

public class CharacterA: Character

{

public CharacterA()

{

Symbol = 'A';

Height = 100;

Width = 120;

}

 

public override void Display(int pointSize)

{

PointSize = pointSize;

Console.WriteLine("{0} (pointsize {1})", Symbol, PointSize);

}

}

 

public class CharacterB: Character

{

public CharacterB()

{

Symbol = 'B';

Height = 100;

Width = 120;

}

 

public override void Display(int pointSize)

{

PointSize = pointSize;

Console.WriteLine("{0} (pointsize {1})", Symbol, PointSize);

}

}

 

public class FlyweightExample

{

private static void Main()

{

// создаём "документ" с текстом

var document = "AABBAB";

var chars = document.ToCharArray();

var f = new CharacterFactory();

// pointSize - это внешнее состояние

var pointSize = 10;

// используем для каждого символа объект-приспособленец

foreach (var c in chars)

{

pointSize++;

var character = f.GetCharacter(c);

character.Display(pointSize);

}

}

}

3.5. СТРУКТУРНЫЕ ШАБЛОНЫ: АДАПТЕР И ФАСАД

Адаптер (Adapter)

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

Дизайн шаблона Адаптер показан на рис. 8.

Рис. 8. Дизайн шаблона Адаптер.

В адаптере четко видно преимущество программирования согласно интерфейсам. Client работает в соответствии с требованиями своей предметной области, эти требования отражены в целевом интерфейсе ITarget (интерфейс, в котором заинтересован клиент). Адаптируемый класс Adaptee обладает требуемой функциональностью, но неподходящим интерфейсом. Adapter реализует интерфейс ITarget и перенаправляет вызовы от Client к Adaptee, изменяя при необходимости параметры и возвращаемые значения.

Существует несколько разновидностей адаптеров. На рис. 8 показан адаптер класса, поскольку он одновременно реализует интерфейс и наследует от адаптируемого класса Adaptee. Альтернативой наследованию может стать агрегация объекта типа Adaptee. В этом случае получится адаптер объекта. Разница в этих двух способах в том, что при наследовании легче переопределить поведение Adaptee, а при агрегации – добавить к Adaptee поведение.

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

using System;

 

public interface ITarget

{

string Request(int i);

}

 

public class Adaptee

{

public double SpecificRequest(double a, double b)

{

return a / b;

}

}

 

public class Adapter: Adaptee, ITarget

{

public string Request(int i)

{

return "Rough: " + (int)Math.Round(SpecificRequest(i, 3));

}

}

 

public class Client

{

private static void Main()

{

var adaptee = new Adaptee();

Console.Write("Before the new standard: ");

Console.WriteLine(adaptee.SpecificRequest(5, 3));

ITarget adapter = new Adapter();

Console.Write("Moving to the new standard: ");

Console.WriteLine(adapter.Request(5));

}

}

Особенность адаптеров в том, что они могут добавлять дополнительное поведение к тому поведению, что специфицируется в ITarget и в Adaptee. Другими словами, адаптеры могут быть прозрачными для клиента и непрозрачными. В примере кода показан последний случай, где Adapter добавляет "Rough:". Эта добавка показывает, что вызов Request() был адаптирован (изменен) перед тем, как был вызван метод SpecificRequest().

Фасад (Façade)

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

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

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

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

Рис. 9. Дизайн шаблона проектирования Фасад.

3.6. ПОРОЖДАЮЩИЕ ШАБЛОНЫ: ПРОТОТИП, ФАБРИЧНЫЙ МЕТОД, ОДИНОЧКА

Прототип (Prototype)

Шаблон Прототип позволяет создавать специальные объекты, ничего не знаю об их классе или деталях создания. Механизм можно описать так: клиенту, инициирующему создание объектов, предоставляются объекты-прототипы. Затем целевые объекты создаются путем клонирования этих объектов-прототипов.

Объект обычно создаётся путем вызова конструктора. Если же используется шаблон Прототип, то клиент знает только об интерфейсе IPrototype, реализующем операции клонирования объекта. Реальный класс объекта клиенту не известен. Прототипы для клонирования могут использоваться произвольное количество раз, сами он при операции клонирования менятся не должны. Хотя существуют различные варианты дизайна данного шаблона, наиболее гибким является вариант с менеджером прототипов, содержащим индексированный список доступных прототипов.

Рис. 10. Дизайн шаблона Прототип.

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

using System;

using System.IO;

using System.Runtime.Serialization.Formatters.Binary;

 

[Serializable]

// Тип T должен быть сериализуемым!

public abstract class Prototype<T>

{

// Поверхностное копирование

public T Clone()

{

return (T)MemberwiseClone();

}

 

// Глубокое копирование

public T DeepCopy()

{

var stream = new MemoryStream();

var formatter = new BinaryFormatter();

formatter.Serialize(stream, this);

stream.Seek(0, SeekOrigin.Begin);

var copy = (T)formatter.Deserialize(stream);

stream.Close();

return copy;

}

}

Фабричный метод (Factory method)

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

Для иллюстрации шаблона Фабричный метод представим магазин, торгующий определенным видом фруктов. Магазин (выступающий в роли клиента) работает с поставщиком (фабричный метод), который в зависимости от времени года импортирует фрукты (объекты) из разных стран (разные классы объектов). Как видим, детали операций импорта от клиента скрыты и независимы.

Рис. 11. UML-диаграмма шаблона Фабричный метод.

Далее приведён код, использующий шаблон Фабричный метод и соответствующий описанному выше гипотетическому примеру.

using System;

 

public interface IProduct

{

string ShipFrom();

}

 

public class ProductFromAfrica: IProduct

{

public String ShipFrom()

{

return "from South Africa";

}

}

 

public class ProductFromSpain: IProduct

{

public String ShipFrom()

{

return "from Spain";

}

}

 

public class DefaultProduct: IProduct

{

public String ShipFrom()

{

return "not available";

}

}

 

public class Creator

{

public IProduct FactoryMethod(int month)

{

if (month >= 4 && month <= 11)

{

return new ProductFromAfrica();

}

if (month == 12 || month == 2 || month == 1)

{

return new ProductFromSpain();

}

return new DefaultProduct();

}

}

 

public class FactoryMethodExample

{

private static void Main()

{

var c = new Creator();

IProduct product;

for (var i = 1; i <= 12; i++)

{

product = c.FactoryMethod(i);

Console.WriteLine("Avocados " + product.ShipFrom());

}

}

}

Одиночка (Singleton)

Шаблон Одиночка гарантирует создание единственного экземпляра объекта некоторого класса.

Необходимость в единичных объектах часто возникает при программировании физических устройств. Если в системе установлен единственный принтер, то нужно быть уверенным, что в программе имеется единственный объект, описывающий этот принтер и предоставляющий доступ к его настройкам. Иначе возможна ситуация рассогласования – различные (возможно, противоречивые) настройки будут выполняться в разных частях программы над одним физическим объектом.

Шаблон Одиночка добавляет функциональность путём модификации существующего класса. Модификация требует следующих шагов.

· Конструктор класса делается закрытым (private).

· Добавляется закрытое статическое поле только для чтения, которое инстанциируется, используя закрытый экземплярный конструктор класса.

· Для досту







Дата добавления: 2015-09-15; просмотров: 525. Нарушение авторских прав; Мы поможем в написании вашей работы!



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

Практические расчеты на срез и смятие При изучении темы обратите внимание на основные расчетные предпосылки и условности расчета...

Функция спроса населения на данный товар Функция спроса населения на данный товар: Qd=7-Р. Функция предложения: Qs= -5+2Р,где...

Аальтернативная стоимость. Кривая производственных возможностей В экономике Буридании есть 100 ед. труда с производительностью 4 м ткани или 2 кг мяса...

Психолого-педагогическая характеристика студенческой группы   Характеристика группы составляется по 407 группе очного отделения зооинженерного факультета, бакалавриата по направлению «Биология» РГАУ-МСХА имени К...

Общая и профессиональная культура педагога: сущность, специфика, взаимосвязь Педагогическая культура- часть общечеловеческих культуры, в которой запечатлил духовные и материальные ценности образования и воспитания, осуществляя образовательно-воспитательный процесс...

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

Ганглиоблокаторы. Классификация. Механизм действия. Фармакодинамика. Применение.Побочные эфффекты Никотинчувствительные холинорецепторы (н-холинорецепторы) в основном локализованы на постсинаптических мембранах в синапсах скелетной мускулатуры...

Шов первичный, первично отсроченный, вторичный (показания) В зависимости от времени и условий наложения выделяют швы: 1) первичные...

Предпосылки, условия и движущие силы психического развития Предпосылки –это факторы. Факторы психического развития –это ведущие детерминанты развития чел. К ним относят: среду...

Studopedia.info - Студопедия - 2014-2024 год . (0.011 сек.) русская версия | украинская версия