Технология разработки программного обеспечения (вторая часть) Поведенческие шаблоны проектирования ПО проф. каф. ОСУ Тузовский А.Ф. Лекция 6.

Презентация:



Advertisements
Похожие презентации
©Павловская Т.А. (СПбГУ ИТМО) Курс «С#. Программирование на языке высокого уровня» Павловская Т.А.
Advertisements

Практическое занятие 6. Функции. Большинство языков программирования используют понятия функции и процедуры. C++ формально не поддерживает понятие процедуры,
Наследование Наследование – это отношение является между классами. class Person { string first_name; int birth_year;... } class Student : Person { float.
Основы информатики Классы Заикин Олег Сергеевич zaikin.all24.org
Технология разработки программного обеспечения (вторая часть) Структурные шаблоны проектирования ПО проф. каф. ОСУ Тузовский А.Ф. Лекция 6.
Высокоуровневые методы информатики и программирования Лекция 10 События.
Массивы 9 класс. Основные теоретические сведения Примеры решения задач.
1 Классы в Java Ключевое слово class означает: Я говорю тебе, как выглядит новый тип объекта. Класс является базовым элементом объектно-ориентированного.
Преобразования типов В языке C/C++ имеется несколько операций преобразования типов. Они используются в случае, если переменная одного типа должна рассматриваться.
Высокоуровневые методы информатики и программирования Лекция 9 Делегаты.
Полиморфизм. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Работа с файлами Сазонов Д.О. ПМиЭММ Часть 2. Тема занятия: Работа с файлами через потоки Для реализации файлового ввода/вывода, необходимо включить в.
Сортировка методом пузырька, выбором (Pascal) Кокарева Светлана Ивановна.
Методология объектно- ориентированного программирования.
САОД кафедра ОСУ 1 Основные абстрактные типы данных Схема процесса создания программ для решения прикладных задач ВУ.
Объектно-ориентированный язык программирования. Выберите в реальной жизни любой объект (холодильник, магнитофон, машина) и опишите его свойства, методы,
Коллекции классов Лекция 12. С помощью коллекций вместо создания структур данных программист использует готовые структуры данных, не заботясь об их реализации.
Полиморфизм Полиморфизм (polymorphism) - последний из трех "китов", на которых держится объектно-ориентированное программирование Слово это можно перевести.
Связи между таблицами являются необходимым элементом структуры БД. Для того, чтобы связь была возможна, таблицы должны иметь общие поля. Чаще всего в одной.
АЛГОРИТМИЗАЦИЯ. Алгоритм Алгоритм – описание конечной последовательности действий, приводящей от исходных данных к нужному результату. Где встречаются.
Транксрипт:

Технология разработки программного обеспечения (вторая часть) Поведенческие шаблоны проектирования ПО проф. каф. ОСУ Тузовский А.Ф. Лекция 6

3. Паттерны поведения Паттерны поведения (поведенческие паттерны) служат для управления различными вариантами поведения системы объектов (классов). 1. Chain Of Responsibility 2. Command 3. State 4. Template Method 5. Mediator 6. Interpreter 7. Iterator 8. Memento 9. Observer 10. Strategy 11.Visitor

Паттерн Command (Команда) Паттерн Command инкапсулирует (скрывает) команды в некотором объекте. Такое инкапсулирование позволяет выполнять различные действия, – Например: управление выбором и последовательностью исполнения команд, возможность постановки команд в очередь, отмена команд и т.д.

Паттерн Command является одним из самых простых и элегантных. Он включает интерфейс с единственной функцию, которая не имеет никаких параметров. Область применения этого паттерна практически безгранична. public interface Command { void Execute(); }

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

Диаграмма паттерна Command

Участвующие элементы Command – Определяет интерфейс для исполнения операции ConcreteCommand – Определяет связывание между объектом- получателем (Receiver) и действием – Реализует исполнение путем вызова соответствующих операций Receiver Client – создает объект ConcreteCommand и устанавливает его получателя Invoker – запрашивает команду выполнить некоторый запрос Receiver – знает, как выполнить операции связанные с обработкой запроса

Пример кода паттерна Command abstract class Command { // абстрактный класс 'Command' protected Receiver receiver; public Command (Receiver receiver) { this.receiver = receiver; } public abstract void Execute (); } class ConcreteCommand : Command { // ' ConcreteCommand' класс public ConcreteCommand (Receiver receiver) : base (receiver) {} public override void Execute () {receiver.Action();} } class Receiver { // 'Receiver' класс public void Action() { Console.WriteLine("Вызван метод Receiver.Action ());} } class Invoker { // 'Invoker' класс private Command _command; public void SetCommand(Command command) { this._command = command; } public void ExecuteCommand() { _command.Execute (); } }

Пример кода использования паттерна Command using System; namespace Command { class MainApp { static void Main() { // Создаем объекты receiver, command, and invoker Receiver receiver = new Receiver(); Command command = new ConcreteCommand(receiver); Invoker invoker = new Invoker (); // Устанавливаем и запускаем команду invoker.SetCommand(command); invoker.ExecuteCommand(); Console.ReadLine(); } } }

Использование паттерна Command для управления копировальным устройством Иерархия команд:

Метод Execute() – в классе RelayOnCommand – включает реле, – в классе MotorOffCommand – выключает электродвигатель. Адреса электродвигателя или реле передаются объекту в качестве аргумента конструктора. При такой структуре объекты Command можно передавать между разными компонентами системы, которые будут вызывать метод Execute(), ничего не зная о том, какую именно команду они представляют. Это приводит к интересным упрощениям.

Система управлялась событиями В зависимости от происходящих в системе событий. – реле замыкаются и размыкаются, – двигатели запускаются и останавливаются, – муфты включаются и выключаются Такие события обнаруживаются датчиками. – Например, когда оптический датчик обнаруживает, что лист бумаги дошел до определенного места на тракте, необходимо включить определенную муфту. Это можно реализовать связав подходящий объект ClutchOnCommand с объектом, управляющим данным оптическим датчиком.

У такой простой структуры есть одно огромное преимущество. Класс Sensor понятия не имеет о том, что делает. Обнаружив событие, он просто вызывает метод Execute() связанного с ним объекта Command. Датчики ничего не знают – ни о муфтах, – ни о реле. – ни о механическом устройстве тракта прохождения бумаги. Их функционирование становится очень простым.

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

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

Использование паттерна Command для обработки транзакций Паттерн Command широко применяется для создания и выполнения транзакций. Например, пишется программа для поддержки базы данных о работниках. Пользователь может выполнять набор операций с этой базой данных: – добавление новых работников; – удаление старых работников; – изменение атрибутов работников.

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

Диаграмма классов по работе с данными о сотруднике (расчет зар. платы)

Объект AddEmployeeTransaction содержит – те же поля, что и объект Employee, и – указатель на объект PayClassification. создаются на основе данных, которые ввел пользователь, который описывает нового работника.

Класс AddEmployeeTransaction

Метод Validate() исследует все данные и убеждается, что они осмысленны. – проверяет синтаксическую и семантическую корректность. – может даже проверить, что данные, участвующие в транзакции, не противоречат текущему состоянию базы данных, например удостовериться в том, что работника с указанным табельным номером не существует. Метод Execute() использует проверенные данные для обновления базы.

Объект Employee будет создаваться и инициализироваться значениями, взятыми из объекта AddEmployeeTransaction. В объект Employee будет также скопирован объект PayClassification (целиком или по ссылке).

Физическое отделение частей программы Шаблон Command позволяет разорвать связи между – частью программы, которая получает данные от пользователя, – частью, которая проверяет и обрабатывает их, и самими бизнес-объектами. Если для добавления новых данных используется графический интерфейс (GUI), то совершенно неправильно включать в GUI код для проверки данных и их последующей обработки. – Наличие такой связанности не позволит использовать код проверки и обработки в других интерфейсах. При включении этого кода в класс AddEmployeeTransaction – он физически отделяется от интерфейса получения данных; – отделяется код, знающий о том, как работать с БД, от самих бизнес-объектов (что еще важнее).

Временное разделение частей программы Получив тем или иным способом данные, совсем не обязательно сразу же вызывать для них методы проверки и обработки. Объекты транзакций можно временно сохранить в списке, а проверить и обработать гораздо позже. Предположим, что в течение дня база данных не должна изменяться. Все изменения следует вносить только между полуночью и часом ночи. – Глупо ждать до полуночи, а потом вводить все команды, стараясь успеть все сделать до часу. Удобно ввести команды в рабочее время, сразу же их проверить, а выполнение отложить до полуночи. Паттерн Command дает нам такую возможность.

Часто паттерн Command включает метод Undo(). В классе, производном от Command, – метод Execute() позволяет запомнить детали выполняемой операции, – метод Undo() дает возможность откатить эту операцию и привести систему в исходное состояние. Использование паттерна Command для выполнения операции UnDo

Рассмотрим программу, которая позволяет пользователю рисовать на экране геометрические фигуры. На панели инструментов есть кнопки – Нарисовать круг, – Нарисовать квадрат, – Нарисовать прямоугольник, – и т. д. На панели инструментов есть кнопка Отменить. Пример использования метода Undo()

Система создает объект DrawCircleCommand и вызывает его метод Execute(). Объект DrawCircleCommand следит за состояние мыши, ожидая, когда пользователь нажмет левую кнопку: – при нажатии он делает точку, где находится указатель мыши, центром круга – начинает рисовать анимированный круг, следя за положением указателя, – при отпускании кнопки рисует круг и сохраняет идентификатор нового круга в своей закрытой переменной. В конце метод Execute() возвращает управление и система помещается отработавший объект DrawCirlceCommand в стек выполненных команд. Пользователь нажал кнопку Нарисовать круг.

Система извлекает из стека объекта Command и вызывает его метод Undo(). Получив сообщение Undo(), объект DrawCircleCommand удаляет круг с тем идентификатором, который в нем хранится, из списка объектов, нарисованных на холсте. Это позволяет без труда реализовать команду отмены практически в любом приложении. Код, знающий, как отменить команду, почти всегда находится рядом с кодом, знающим, как ее выполнить. Позже пользователь нажал кнопку Отменить

Использование паттерна Command для имитации многопоточности Одно из интересных применений паттерна Command – это его использование в паттерне Active Object (Активный объект). Давно используется в тысячах промышленных систем для организации простого многопоточного ядра. Идея очень проста и показана на листинги последующих слайдов.

Класс ActiveObjectEngine using System.Collections; public class ActiveObjectEngine { ArrayList itsCommands = new ArrayList(); public void AddCommand(Command c) { itsCommands.Add(c); } public void Run() { while (itsCommands.Count > 0) { Command c = (Command) itsCommands[0]; itsCommands.RemoveAt(0); c.Execute(); } }} ИнтерфейсCommand.cs public interface Command { void Execute(); }

Объект ActiveObjectEngine хранит связанный список объектов Command. Пользователь может – добавлять в него новые команды – вызывать метод Run(). Метод Run() – проходит по списку команд; – выполняет каждую встретившуюся команду; – затем удаляет ее.

Что произойдет, если какой-то из находившихся в списке объектов Command поместит себя обратно в список – получается бесконечный цикл. Список никогда не опустеет и метод Run() будет работать вечно.

Рассмотрим тест Он создает объект SleepCommand, передавая его конструктору среди прочего и величину задержки 1000 мс. Затем объект SleepCommand помещается в ActiveObjectEngine. Тест ожидает, что после вызова Run() должно пройти не менее 1000 мс.

Взглянем на этот тест внимательнее. У конструктора SleepCommand есть три аргумента. – Первый – время задержки в миллисекундах. – Второй – объект ActiveObjectEngine, внутри которого будет работать команда. – А третий, wakeup, – еще одна команда, которую следует вызвать при возобновлении работы, то есть по прошествии указанного числа миллисекунд.

Класс SleepCommand using System; public class SleepCommand : Command { private Command wakeupCommand = null; private ActiveObjectEngine engine = null; private long sleepTime = 0; private DateTime startTime; private bool started = false; public SleepCommand( long milliseconds, ActiveObjectEngine e, Command wakeupCommand) { sleepTime = milliseconds; engine = e; this.wakeupCommand = wakeupCommand; } public void Execute() { DateTime currentTime = DateTime.Now; if (!started) { started = true; startTime = currentTime; engine.AddCommand(this); } else { TimeSpan elapsedTime = currentTime - startTime; if (elapsedTime.TotalMilliseconds < sleepTime) { engine.AddCommand(this); } else { engine.AddCommand(wakeupCommand); } }

При выполнении объект проверяет, исполнялся ли он раньше, и если нет, то запоминает время начала работы. – Если задержка еще не истекла, то объект помещает себя обратно в ActiveObjectEngine. – В противном случае в ActiveObjectEngine помещается команда wakeup. Данная программа аналогична многопоточной программе, ожидающей события. Когда поток в многопоточной программе ждет события, он обычно делает вызов ОС, который блокирует поток, пока событие не произойдет.

Показанная выше программа не блокируется. Но если ожидаемое событие не произошло в течение времени elapsedTime.TotalMilliseconds, то поток просто помещает себя назад в объект ActiveObjectEngine. Построение многопоточных систем с использованием этой техники было и остается весьма распространенной практикой. – Такие потоки называются исполняемыми до завершения (Run-to-Completion – RTC); – каждый экземпляр Command отрабатывает до конца, прежде чем запускается следующий экземпляр. Аббревиатура RTC подразумевает, что экземпляры Command не блокируют программу.

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

Программа DelayedTyper using System; public class DelayedTyper : Command { private long itsDelay; private char itsChar; private static bool stop = false; private static ActiveObjectEngine engine = new ActiveObjectEngine(); private class StopCommand : Command { public void Execute() { DelayedTyper.stop = true; } } public static void Main(string[] args) { engine.AddCommand(new DelayedTyper(100, 1)); engine.AddCommand(new DelayedTyper(300, 3)); engine.AddCommand(new DelayedTyper(500, 5)); engine.AddCommand(new DelayedTyper(700, 7)); Command stopCommand = new StopCommand(); engine.AddCommand( new SleepCommand(20000, engine, stopCommand)); engine.Run(); } public DelayedTyper(long delay, char c) { itsDelay = delay; itsChar = c; } public void Execute() { Console.Write(itsChar); if (!stop) DelayAndRepeat(); } private void DelayAndRepeat() { engine.AddCommand( new SleepCommand(itsDelay, engine, this)); }

Класс DelayedTyper реализует интерфейс Command. Метод Execute() – печатает символ, переданный конструктору, – проверяет флаг stop – если флаг stop не равен true, вызывает метод DelayAndRepeat(). Метод DelayAndRepeat() – создает объект SleepCommand с задержкой, величина которой передана конструктору, – включает этот объект в список объекта ActiveObjectEngine.

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

Поведение этого объекта Command легко предсказать. – Он работает в цикле, печатая заданный символ и ожидая истечения задержки. – Выход из цикла происходит, когда флаг stop = true. Метод Main () – создает несколько объектов DelayedTyper, каждый со своим символом и задержкой, – помещает их в ActiveObjectEngine, – добавляет туда же команду SleepCommand, которая по истечении некоторого времени установит флаг stop. Если запустить эту программу, то будет напечатана строка, содержащая символы 1, 3, 5 и 7. При повторном запуске будет напечатана другая строка, состоящая из тех же символов.

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

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

Участники паттерна State Context – Определяет интерфейс для клиентов – Поддерживает объект наследника ConcreteState, определяющего текущее состояние State – Определяет интерфейс для инкапсуляции поведения, связанного с состоянием Context. ConcreteState – наследник интерфейса Context реализует поведение, связанное с конкретным состоянием Context.

Пример использования паттерна State using System; namespace State { class MainApp { static void Main() { // Создаём контекст в определенном состоянии Context с = new Context(new ConcreteStateA()); // Вызываем операции, которые переключают состояние с.Request () ; Console.ReadLine() ; } // State абстрактный класс abstract class State { public abstract void Handle(Context context); }

// ConcreteStateA класс class ConcreteStateA : State { public override void Handle (Context context) { context.State = new ConcreteStateB ();} } // ConcreteStateB class class ConcreteStateB : State { public override void Handle (Context context) { context. State = new ConcreteStateA () ; } } // Context класс class Context { private State state; public Context(State state) { this.State = state;} public State State { get { return _state; } set { _state = value; Console.WriteLine("Состояние: + state.GetType().Name); } public void Request() { state.Handle(this) ;} }

Паттерн Mediator (Посредник) Паттерн Mediator используется для согласования изменений состояний набора объектов с помощью одного объекта. Вместо раскидывания логики поведения по разным классам данный паттерн инкапсулирует логику управления изменением состояний в рамки одного класса.

UML диаграмму паттерна Mediator (Посредник)

Участники паттерна Mediator (Посредник) Класс Mediator – Определяет интерфейс для общения с объектами Colleague ConcreteMediator – Реализует совместное поведение путем координирования объектов Colleague – Знает и поддерживает своих Colleague Colleague классы – Каждый Colleague класс знает своего Mediator – Каждый Colleague общается со своим медиатором

Пример кода (показывает принцип работы Mediator) using System; namespace Mediator { class MainApp { static void Main() { ConcreteMediator m = new ConcreteMediator (); ConcreteColleague1 c1 = new ConcreteColleague1 (m) ; ConcreteColleague2 c2 = new ConcreteColleague2 (m) ; m.Colleague1 = c1; m.Colleague2 = c2 ; c1.Send("Как дела?"); c2.Send("Хорошо спасибо"); Console.ReadLine(); } // 'Mediator' абстрактный класс abstract class Mediator { public abstract void Send(string message, Colleague colleague); } // 'ConcreteMediator' класс class ConcreteMediator : Mediator { private ConcreteColleague1 _colleague1; private ConcreteColleague2 _colleague2; public ConcreteColleaguel Colleague1 { set { _colleague1 = value; } } public ConcreteColleague2 Colleague2 { set { _colleague2 = value; } } public override void Send(string message, Colleague colleague) { if (colleague == _colleague1) _colleague2.Notify(message) else _colleaguel.Notify(message); } } // 'Colleague' абстрактный класс abstract class Colleague { protected Mediator mediator; public Colleague(Mediator mediator) // Конструктор { this.mediator = mediator; } } // 'ConcreteColleague1' класс class ConcreteColleague1 : Colleague { // Конструктор public ConcreteColleaguel(Mediator mediator) : base(mediator) { } public void Send(string message) { mediator.Send(message, this); } public void Notify(string message) { Console.WriteLine("Colleague1 получил сообщение: "+ message); } } // 'ConcreteColleague2' класс class ConcreteColleague2 : Colleague { // Конструктор public ConcreteColleague2 (Mediator mediator) : base (mediator } {} public void Send(string message) { mediator.Send(message, this); } public void Notify(string message) { Console.WriteLine("Colleague2 получил сообщение: " + message); } }

Паттерны Template Methоd (Шаблонный метод) и Strategy (Стратегия) В начале 1990-х годов, когда ОО технологии только зарождались, всех захватила идея наследования. – Это отношение обещало грандиозные перспективы. С помощью наследования можно было программировать только различия! – имея класс, делающий нечто полезное, можно создать его подкласс и изменить лишь те части, которые нас не устраивали. Появилась возможность повторно использовать код, просто унаследовав его! Можно организовывать целые иерархии программных конструкций, в которых на каждом уровне использовался код с предыдущих уровней. Открылся прекрасный новый мир.

Но, как и большинство прекрасных новых миров, этот на поверку тоже оказался не вполне пригодным к обитанию. К 1995 году стало понятно, что наследованием очень просто злоупотребить, а обходится такое злоупотребление крайне дорого. Гамма, Хелм, Джонсон и Влиссидес советуют: «Отдавайте предпочтение композиции объектов, а не наследованию классов». стали реже применять наследование, чаще заменять его композицией или делегированием.

Иллюстрация различий между наследованием и делегированием Для решения сходных задач (и нередко взаимозаменяемо) используют шаблоны: – Шаблон Template Method (Шаблонный метод) и – Шаблон Стратегия (Strategy) Иллюстрируют различие между наследованием и делегированием. – в Шаблонном методе применяется наследование, – в Стратегии – делегирование.

Решаемая задача Шаблоны Template Methоd и Strategy решают задачу отделения общего алгоритма от конкретного контекста. – это очень часто требуется при проектировании ПО. Задача: имеется алгоритм общего вида, применимый к разным ситуациям. Нужно сделать, чтобы этот алгоритм не зависел от деталей реализации. – алгоритм и конкретная реализация должны зависеть только от абстракций. – в соответствии с принципом инверсии зависимости (Dependency-Inversion Principle)

Пример использования паттерна Template Method Например, многие программы имеют следующую структуру: Initialize(); // инициализация приложения while (!Done()) {// основной цикл doWork(); // делаем что-то полезное. } Cleanup(); // делаем зачистку

Сначала выполняется инициализация приложения. Затем входим в главный цикл, где программа делает то, для чего написана. – например, обработка событий GUI или записей базы данных. Когда все сделано, выполняется выход из главного цикла Делается зачистка.

Пример программы using System; using System.IO; public class FtoCRaw { public static void Main(string[] args) { bool done = false; while (!done) { string fahrString = Console.In.ReadLine(); if (fahrString == null || fahrString.Length == 0) done = true; else { double fahr = Double.Parse(fahrString); double celcius = 5.0/9.0*(fahr - 32); Console.Out.WriteLine(F={0}, C={1},fahr,celcius); } Console.Out.WriteLine(ftoc exit); } }

В данной программе имеются все элементы рассмотренного главного цикла Небольшая инициализация, Содержательная работа в цикле, Затем очистка и выход. Отделить базовую структуру от конкретной программы ftoc позволяет паттерн Template Methоd.

Паттерн Template Methоd Алгоритм, описывающий общую структуру, описывается в имеющем реализацию методе абстрактного базового класса. Детали выносятся в абстрактные методы. Например, общая структура программы очень распространена, – ее можно инкапсулировать в класс Application. – делать каждую новую программе производной от класса Application.

Класс Application public abstract class Application { private bool isDone = false; protected abstract void Init(); protected abstract void Idle(); protected abstract void Cleanup(); protected void SetDone() { isDone = true; } protected bool Done() { return isDone; } public void Run() { Init(); while (!Done()) Idle(); Cleanup(); }

Общая структура приложения с главным циклом Цикл находится в реализованном методе Run(). Работа программы вынесена в абстрактные методы Init(), Idle() и Cleanup(). – Метод Init() берет на себя инициализацию. – Метод Idle() выполняет основную работу программы вызывается до тех пор, пока Done() возвращает false. – Метод Cleanup() отвечает за очистку перед выходом.

Программа с использованием шаблона Template Methоd using System; using System.IO; public class FtoCTemplateMethod : Application { private TextReader input; private TextWriter output; public static void Main (string[] args) { new FtoCTemplateMethod().Run(); } protected override void Init() { input = Console.In; output = Console.Out; } protected override void doWork() { string fahrString = input.ReadLine(); if (fahrString == null || fahrString.Length == 0) SetDone(); else { double fahr = Double.Parse(fahrString); double celcius = 5.0/9.0*(fahr - 32); output.WriteLine(F={0}, C={1}, fahr, celcius); } protected override void Cleanup() { output.WriteLine(ftoc exit); } }

Анализ использования шаблона Применять Template Methоd для данного конкретного приложения не обосновано. – лишь усложняет и увеличивает программу. Инкапсуляция главного цикла всех возможных приложений поначалу кажется хорошей идеей, – но ее практическое воплощение в данном случае не приносит никаких реальных результатов. Паттерны проектирования – хорошие способы. – могут помочь в решении многих задач проектирования. Однако не нужно злоупотреблять их использованием. Хотя к данному случаю можно применить Template Methоd, но использовать его не стоит. – Издержки превышают выгоду.

Класс для пузырьковой сортировки public class BubbleSorter { static int operations = 0; public static int Sort(int [] array) { operations = 0; if (array.Length = 0; nextToLast--) for (int index = 0; index array[index+1]) Swap(array, index); operations++; }

Класс BubbleSorter знает, как сортировать массив целых чисел, применяя алгоритм пузырьковой сортировки. Метод Sort() содержит сам алгоритм пузырьковой сортировки Два вспомогательных метода – Swap() и – CompareAndSwap() – посвящены деталям, связанным с целыми числами и массивами.

С помощью паттерна Template Methоd можно выделить алгоритм пузырьковой сортировки в абстрактный базовый класс BubbleSorter. Он содержит реализацию метода Sort(), который вызывает абстрактные методы OutOfOrder() и Swap(). – Метод OutOfOrder() сравнивает два соседних элемента массива и возвращает true, если они расположены не по порядку. – Метод Swap() переставляет местами два соседних элемента массива.

Метод Sort() ничего не знает о массиве, ему все равно, какие в нем хранятся объекты. Он вызывает OutOfOrder(), передавая ему индекс элемента в массиве, и узнает, нужно ли переставить соседние элементы.

Абстрактный класс сортировки public abstract class BubbleSorter { private int operations = 0; protected int length = 0; protected int DoSort() { operations = 0; if (length = 0; nextToLast--) for (int index = 0; index

класс IntBubbleSorter будет сортировать массивы целых чисел; класс DoubleBubbleSorter – массивы чисел с двойной точностью.

Реализация класса IntBubbleSorter public class IntBubbleSorter : BubbleSorter { private int[] array = null; public int Sort(int[] theArray) { array = theArray; length = array.Length; return DoSort(); } protected override void Swap(int index) { int temp = array[index]; array[index] = array[index + 1]; array[index + 1] = temp; } protected override bool OutOfOrder(int index) { return (array[index] > array[index + 1]); }

Реализация класса DoubleBubbleSorter public class DoubleBubbleSorter : BubbleSorter { private double[] array = null; public int Sort(double[] theArray) { array = theArray; length = array.Length; return DoSort(); } protected override void Swap(int index) { double temp = array[index]; array[index] = array[index + 1]; array[index + 1] = temp; } protected override bool OutOfOrder(int index) { return (array[index] > array[index + 1]); }

Выводы Паттерн Template Methоd – пример классической формы повторного использования в ОО программировании. Обобщенный алгоритм помещается в базовый класс, который наследуется в различных конкретных контекстах. Такой способ имеет свои недостатки: наследование – очень сильное отношение.

Подклассы неразрывно связаны со своими базовыми классами. – Например, методы OutOfOrder и Swap, реализованные в классе IntBubbleSorter, – это то, что необходимо алгоритму сортировки. Использовать их в других алгоритмах уже нельзя. – Унаследовав класс IntBubbleSorter от BubbleSorter, они навечно привязаны один к другому. Паттерн Стратегия предлагает иной подход.

Паттерн Strategy (Стратегия) Паттерн Strategy решает проблему инверсии зависимости совсем по- другому. Рассмотрим класс Application использующий паттерн Template Methоd. Общий алгоритм работы приложения помещается – не в абстрактный базовый класс, – а в конкретный класс ApplicationRunner.

Паттерн Strategy (Стратегия) Абстрактные методы, которые может вызывать общий алгоритм, определим в интерфейсе Application. 1. создадим производный от (интерфейса) Application класс FtoCStrategy и 2. передадим его в ApplicationRunner. Таким образом, ApplicationRunner делегирует содержательную работу этому интерфейсу.

Интерфейс Application public interface Application { void Init(); void doWork(); void Cleanup(); bool Done(); }

Класс ApplicationRunner public class ApplicationRunner { private Application itsApplication = null; public ApplicationRunner(Application app) {itsApplication = app;} public void run(){ itsApplication.Init(); while (!itsApplication.Done()) itsApplication.doWork(); itsApplication.Cleanup(); }

Класс FtoCStrategy using System; using System.IO; public class FtoCStrategy : Application { private TextReader input; private TextWriter output; private bool isDone = false; public static void Main(string[] args) { FtoCStrategy fcs = new FtoCStrategy(); ApplicationRunner apr = new ApplicationRunner(fcs); apr.run(); } public void Init() { input = Console.In; output = Console.Out; } public void doWork() { string fahrString = input.ReadLine(); if (fahrString == null || fahrString.Length == 0) isDone = true; else { double fahr = Double.Parse(fahrString); double celcius = 5.0/9.0*(fahr - 32); output.WriteLine(F={0}, C={1}, fahr, celcius); }}

Недостатки Strategy по сравнению с Template Methоd 1. В паттерне Strategy больше классов и выше уровень косвенности, чем в Template Methоd. 2. Делегирование по указателю в ApplicationRunner обходится с точки зрения времени и памяти чуть дороже, чем наследование.

Достоинства Strategy по сравнению с Template Methоd Если нужно запускать много приложений, то – можно использовать один экземпляр ApplicationRunner и – передавать ему различные реализации Application, что позволит сэкономить память.

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

Интерфейс SortHandler public interface SortHandler { void Swap(int index); bool OutOfOrder(int index); int Length(); void SetArray(object array); }

Класс BubbleSorter public class BubbleSorter { private int operations = 0; private int length = 0; private SortHandler itsSortHandler = null; public BubbleSorter(SortHandler handler) { itsSortHandler = handler; } public int Sort(object array) { itsSortHandler.SetArray(array); length = itsSortHandler.Length(); operations = 0; if (length = 0; nextToLast--) for (int index = 0; index

Класс IntSortHandler public class IntSortHandler : SortHandler { private int[] array = null; public void Swap(int index) { int temp = array[index]; array[index] = array[index + 1]; array[index + 1] = temp; } public void SetArray(object array) { this.array = (int[]) array; } public int Length() { return array.Length; } public bool OutOfOrder(int index) { return (array[index] > array[index + 1]);} }

В шаблоне Strategy класс IntSortHandler ничего не знает о BubbleSorter и никак не зависит от реализации пузырьковой сортировки. В шаблоне Template Methоd дело обстоит иначе. – IntBubbleSorter напрямую зависит от класса BubbleSorter, содержащего алгоритм пузырьковой сортировки. – Это отчасти нарушает принцип инверсии зависимости. – Реализация методов Swap и OutOfOrder зависит от алгоритма пузырьковой сортировки.

В паттерне Strategy такой зависимости нет – класс IntSortHandler можно использовать и с другими реализациями сортировщика, а не только с BubbleSorter. Например, можно создать вариант пузырьковой сортировки, который прекращал бы работу, как только на очередном проходе по массиву выясняется, что он уже отсортирован. Такой класс QuickBubbleSorter мог бы воспользоваться классом IntSortHandler или любым другим, производным от SortHandler.

Класс QuickBubble Sorter public class QuickBubbleSorter { private int operations = 0; private int length = 0; private SortHandler itsSortHandler = null; public QuickBubbleSorter(SortHandler handler) { itsSortHandler = handler; } public int Sort(object array) { itsSortHandler.SetArray(array); length = itsSortHandler.Length(); operations = 0; if (length = 0 && !thisPassInOrder; nextToLast--) { thisPassInOrder = true; //potenially. for (int index = 0; index

Преимущество Strategy по сравнению с Template Methоd Template Methоd позволяет подставлять в общий алгоритм различные детальные реализации, Strategy (в полном соответствии с принципом DIP) кроме этого разрешает использовать любую детальную реализацию в различных общих алгоритмах.

Выводы Паттерн Template Methоd легко использовать на практике, но он недостаточно гибок. Паттерн Strategy обладает нужной гибкостью, но приходится вводить дополнительный класс, создавать дополнительный объект и инкорпорировать его в систему. Выбор между этими паттернами зависит от того, нужна ли гибкость Strategy или готовы удовольствоваться простотой Template Methоd.

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

Класс QuickEntryMediator Принимает объекты TextBox и ListBox. Предполагается, что пользователь будет вводить в TextBox префиксы строк, находящихся в ListBox Класс автоматически выбирает первый элемент ListBox, который начинается с префикса, введенного в TextBox. Если значение в поле TextBox равно null или префикс не соответствует никакому элементу ListBox, то выделение в ListBox снимается. В этом классе нет открытых методов. Нужно просто создать объект класса и QuickEntryMediator и забываете о его существовании. Например: TextBox t = new TextBox(); ListBox l = new ListBox(); QuickEntryMediator qem = new QuickEntryMediator(t, l);

Kласс QuickEntryMediator using System; using System.Windows.Forms; public class QuickEntryMediator { private TextBox itsTextBox; private ListBox itsList; public QuickEntryMediator(TextBox t, ListBox l) { itsTextBox = t; itsList = l; itsTextBox.TextChanged += new EventHandler(TextFieldChanged); } private void TextFieldChanged(object source, EventArgs args) { string prefix = itsTextBox.Text; if (prefix.Length == 0) { itsList.ClearSelected(); return; } ListBox.ObjectCollection listItems = itsList.Items; bool found = false; for (int i = 0; found == false && i < listItems.Count; i++) { object o = listItems[i]; string s = o.ToString(); if (s.StartsWith(prefix)) { itsList.SetSelected(i, true); found = true; } if (!found) { itsList.ClearSelected(); } }

Структура класса QuickEntryMediator Конструктору экземпляра QuickEntryMediator передаются ссылки на ListBox и TextBox. QuickEntryMediator назначает обработчик события TextChanged для объекта TextBox. при любом изменении текста вызывает метод TextFieldChanged, который ищет в списке ListBox элемент, начинающийся с текущего значения текстового поля, и выделяет его. Пользователи классов ListBox и TextField понятия не имеют о существовании этого Посредника. Он находится в сторонке и незаметно накладывает свою политику на объекты, не спрашивая у них разрешения и даже не ставя их в известность.

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

Паттерн Наблюдатель (Observer)

Паттерн Наблюдатель

Абстрактный сервер, адаптер и мост