|
|||||||
АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомДругоеЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция |
Учасники паттерна. - визначає інтерфейс для обробки запитів;1. Handler – обробник: - визначає інтерфейс для обробки запитів; - (необов’язково) реалізує зв’язок з наступником. 2. ConcreteHandler – конкретний обробник: - обробляє запит, за який відповідає; - має доступ до свого наступника; - якщо ConcreteHandler здатний обробити запит, то так і робить, якщо не може, то направляє його своєму наступнику. 3. Client – клієнт: - надсилає запит деякому об’єкту ConcreteHandler в ланцюжку. Відносини учасників паттерна. Послідовність роботи системи із використанням паттерна: 1. Базовий клас має покажчик на наступний обробник. 2. Кожний похідний клас реалізує свій внесок в обробку запиту. 3. Якщо запит повинен бути «переданий далі», то похідний клас «викликає» базовий клас, який за допомогою покажчика делегує запит далі. 4. Клієнт (або третя сторона) створює ланцюжок одержувачів (який може мати посилання з останнього вузла на кореневий вузол). 5. Клієнт передає кожний запит на початок ланцюжка. Особливості програмної реалізації. При реалізації необхідно врахувати наступні моменти [18]: 1. Реалізація ланцюжка спадкоємців. Є два способи реалізувати такий ланцюжок: - визначити нові зв’язки (зазвичай, це робиться в класі Handler, але можна й у ConcreteHandler); - використовувати існуючі зв’язки. 2. З’єднання спадкоємців. Якщо готових посилань, придатних для створення ланцюжка, немає, то їх прийдеться ввести. В такому випадку клас Handler не тільки визначає інтерфейс запитів, але ще і зберігає посилання на спадкоємця. Отже в оброблювача з’являється можливість визначити реалізацію операції HandleRequest за замовчуванням – перенаправлення запиту спадкоємцю (якщо такий існує). Якщо підклас ConcreteHandler не зацікавлений у запиті, то йому і не треба заміщати цю операцію, оскільки за замовчуванням запит відправляється далі. 3. Представлення запитів. Представляти запити можна по-різному. В найпростішій формі запит жорстко кодується як виклик деякої операції. Це зручно і безпечно, але переадресовувати тоді можна тільки фіксований набір запитів, визначених у класі Handler. Альтернатива – використовувати одну функцію-оброблювач, якій передається код запиту (скажемо, ціле число або рядок). Так можна підтримати заздалегідь невідому кількість запитів. Єдина вимога полягає в тому, що відправник і одержувач повинні домовитися про спосіб кодування запиту. Це більш гнучкий підхід, але при реалізації потрібно використовувати умовні оператори для роздачі запитів по їх коду. Крім того, не існує безпечного, з погляду типів, способу передачі параметрів, тому упаковувати і розпаковувати їх приходиться вручну. Очевидно, що це не так безпечно, як прямий виклик операції. Результати використання. Результати застосування паттерна можуть бути представлені так [30]: - логічна ізоляція відправника запиту від одержувачів; - об’єкт спрощується, оскільки йому не потрібно ні знати структуру ланцюжка, ні зберігати прямі посилання на її елементи. Цей паттерн звільняє об’єкт від необхідності знати, хто конкретно обробить його запит. Відправнику й одержувачу нічого невідомо один про одного, а доданому в ланцюжок об’єкту – про структуру ланцюжка. Таким чином, Ланцюжок відповідальностей допомагає спростити взаємозв’язки між об’єктами. Замість того, щоб зберігати посилання на всі об’єкти, що можуть стати одержувачами запиту, об’єкт повинний мати інформацію лише про свого найближчого спадкоємця; - можливість динамічного додавання або видалення обов’язків за допомогою зміни елементів ланцюжка або їх порядку. Ланцюжок відповідальностей дозволяє підвищити гнучкість розподілу обов’язків між об’єктами. Додати або змінити обов’язки по обробці запиту можна, включивши в ланцюжок нових учасників або змінивши його якимось іншим чином. Цей підхід можна сполучити зі статичним породженням підкласів для створення спеціалізованих оброблювачів. Область застосування і недоліки паттерна: - часто використовується в графічних середовищах для обробки подій (клацання мишею, події клавіатури і т.д.); - обробка запиту не гарантована; якщо подія не буде оброблена жодним об’єктом, вона просто виходить з кінця ланцюжка (це може бути як достоїнством, так і недоліком). Оскільки в запиту немає явного одержувача, те немає і гарантій, що він узагалі буде оброблений: він може досягти кінця ланцюжка і пропасти. Неопрацьованим запит може виявитися й у випадку неправильної конфігурації ланцюжка; - ланцюжки можуть ускладнювати відстеження запитів і налаштування системи.
Паттерн Ітератор (Iterator) Призначення. Паттерн Ітератор – це паттерн поведінки, що надає спосіб послідовного доступу до елементів складеного об’єкта, не розкриваючи його внутрішнього представлення. Відомий також під ім’ям Cursor (курсор) [12]. Частота використання Мотивація застосування. Існує багато способів створення колекцій. Об’єкти можна розмістити в контейнері Array, Stack, List, Hashtable. Кожний спосіб має свої достоїнства і недоліки. Але в якийсь момент клієнту потрібно перебрати всі ці об’єкти, і коли це станеться, чи збираєтеся ви розкривати реалізацію колекції? Сподіваємося, ні! Це було б вкрай непрофесійно. В цій темі ви дізнаєтеся, як надати клієнту механізм перебору об’єктів без розкриття інформації про спосіб їх зберігання. Використовуйте паттерн Ітератор для [14] : - доступу до вмісту агрегованих об’єктів без розкриття їх внутрішнього подання; - підтримки декількох активних обходів одного і того ж агрегованого об’єкта; - надання однакового інтерфейсу з метою обходу різних агрегованих структур (тобто для підтримки поліморфної ітерації). Проблемні предметні області. 1. Наприклад, клас List міг би передбачити клас Listlterato r (рис. 6.5).
Рис. 6.5. Створення Ітератора для списку
Перш ніж створювати екземпляр класу Listlterator, необхідно мати список, що підлягає обходу. З об’єктом ListІterator можна послідовно відвідати всі елементи списку. Операція CurrentItem повертає поточний елемент списку, операція First ініціалізує поточний елемент першим елементом списку, Next робить поточним наступний елемент, a IsDone перевіряє, чи обхід опинився за останнім елементом, якщо так, то обхід завершений. Відділення механізму обходу від об’єкта List дозволяє визначати ітератори, що реалізують різні стратегії обходу, не перераховуючи їх в інтерфейсі класу List. Зауважимо: між ітератором і списком є тісний зв’язок, клієнт повинен мати інформацію, що він обходить саме список, а не якусь іншу агреговану структуру. Тому клієнт прив’язаний до конкретного способу агрегування. Було б краще, якщо б ми могли змінювати клас агрегату, не чіпаючи код клієнта. Це можна зробити, узагальнивши концепцію Ітератора та розглянувши поліморфну ітерацію. Наприклад, припустимо, що у нас є ще клас SkipList, що реалізує список з пропусками. Список з пропусками (skiplist) – це імовірнісна структура даних, яка за характеристиками нагадує збалансоване дерево. Нам потрібно навчитися писати код, здатний працювати з об’єктами як класу List, так і класу SkipList (рис. 6.6).
Рис. 6.6. Розширення реалізації Ітератора
Визначимо клас AbstractList, в якому оголошено загальний інтерфейс для маніпулювання списками. Ще нам знадобиться абстрактний клас Iterator, який визначає загальний інтерфейс ітерації. Потім ми змогли б визначити конкретні підкласи класу Iterator для різних реалізацій списку. В результаті механізм ітерації виявляється не залежним від конкретних агрегованих класів. Залишається зрозуміти, як створюється ітератор. Оскільки ми хочемо написати код, який не залежить від конкретних підкласів List, то не можна просто інстанціювати конкретний клас. Замість цього ми доручимо самим об’єктам-спискам створювати для себе підходящі ітератори, ось чому потрібна операція CreateІterator, за допомогою якої клієнти зможуть запитувати об’єкт-ітератор. CreateІterator – це приклад використання паттерну Фабричний метод. В даному випадку він служить для того, щоб клієнт міг запитати в об’єкта-списку підходящий ітератор. Застосування фабричного методу призводить до появи двох ієрархій класів – однієї для списків, іншої для ітераторів. Фабричний метод CreateІterator «пов’язує» ці дві ієрархії. 2. Якщо ви хочете одночасно підтримувати чотири види структур даних (масив, бінарне дерево, зв’язаний список і хеш-таблицю) та три алгоритму (сортування, пошук та злиття), то традиційний підхід вимагатиме 12 варіантів конфігурацій (чотири рази по три), в той час як узагальнене програмування вимагає лише 7 (чотири плюс три) [14]. А якщо все-таки дозволити класам колекцій реалізувати як управління об’єктами, так і методи перебору? Так, це призведе до збільшення кількості методів колекції, ну і що? Чим це погано? Щоб зрозуміти, чому це погано, спочатку необхідно усвідомити один факт: доручаючи класу не тільки його безпосереднє завдання (управління колекцією об’єктів), але й додаткові завдання (перебір), ми створюємо дві можливі причини для зміни. Тепер може змінитися як внутрішня реалізація колекції, так і механізм перебору. Черговий принцип проектування полягає в наступному: змін у класах необхідно, по можливості, уникати – модифікація коду, зазвичай, супроводжується масою проблем. Наявність двох причин для зміни підвищує імовірність того, що клас зміниться в майбутньому, а якщо це все-таки відбудеться – зміни вплинуть на два аспекти архітектури. Принцип указує на те, що кожному класу повинен бути виділений один – і тільки один обов’язок. Як це часто буває, в реальному житті все трохи складніше: розподіл обов’язків в архітектурі є однією з найбільш складних завдань. Наш мозок схильний об’єднувати аспекти поведінки навіть у тому випадку, якщо в дійсності мова йде про два різні обов’язки. Єдиний шлях до її вирішення – аналіз архітектури і відстеження можливих причин зміни класів у ході зростання системи. 3. Нехай виникла необхідність розширити програмну систему, що описує процес замовлень у кафе, об’єднавши реалізацію меню для сніданків і обідів [10]. Реалізація меню для сніданків зберігалася в об’єкті Array, а обідів у ArrayLіst. З кожним елементом меню пов’язується назва, опис і ціна. Для обох реалізацій створено занадто багато коду, щоб переписувати його заново. Виникає питання, яким чином одноманітно забезпечити роботу з колекціями даних різних типів. Щоб зрозуміти, які складності виникають із двома різними представленнями меню, необхідно реалізувати клієнта, що використовує обидва меню. Припустимо, нова компанія, поставила задачу забезпечити роботу клієнтського класу, який би використовував вже існуючі колекції даних і їх реалізації. Специфікація вимагає, щоб новий клас міг при необхідності надрукувати скорочене меню і навіть визначити, чи є блюдо вегетаріанським. При цьому виникають наступні проблеми: 1. Щоб вивести повне меню, необхідно викликати метод getMenuІtem () для всіх елементів обох реалізацій. Зверніть увагу: методи повертають різні типи. 2. Щоб вивести меню обідів, ми перебираємо елементи контейнера ArrayLіst, а для виведення меню сніданків перебираються елементи Array. 3. Реалізація кожного методу буде представляти собою варіацію на цю тему – вміст двох меню буде перебиратися в двох різних циклах. А якщо раптом додасться новий ресторан зі своєю реалізацією, то в програмі будуть використовуватися три різних цикли і так далі. Добре б знайти механізм, що дозволяє реалізувати єдиний інтерфейс для двох меню (вони і так досить близькі, якщо не брати до уваги тип даних, що повертається методом getMenuіtems ()). Це дозволить звести до мінімуму конкретні посилання, а також позбутися від повторення циклів при переборі елементів меню. Використовуючи один із головних принципів ООП, необхідно інкапсулювати те, що змінюється. Зрозуміло, що змінюється в даному випадку: механізм перебору для різних колекцій об’єктів (елементів меню). Для перебору елементів ArrayLіst використовуються методи sіze () і get () (рис. 6.7):
Рис. 6.7. Схема перебору елементів об’єкту ArrayLіst
2. Для перебору елементів масиву використовується поле length об’єкта Array і синтаксис вибірки елементів масиву (рис. 6.8). Рис. 6.8. Схема перебору елементів об’єкту Array
Тепер необхідно створити об’єкт (ітератор), що інкапсулює механізм перебору об’єктів у колекції. Спробуємо зробити це для ArrayLіst (рис. 6.9).
Рис. 6.9. Створення ітератора для об’єкта ArrayLіst
Тепер зробимо таке ж саме для Array (рис. 6.10).
Рис. 6.10. Створення ітератора для об’єкта Array
Схоже, інкапсуляція перебору елементів цілком реальна. Для розв’язання цієї задачі існує паттерн проектування, що називається Ітератор. Перше, що необхідно знати про паттерн Ітератор – це те, що він залежить від спеціального інтерфейсу (припустимо, Іterator). Одна з можливих форм інтерфейсу Іterator (рис. 6.11): - метод hasNext () перевіряє, чи залишилися в колекції елементи для перебору; - метод next () повертає наступний об’єкт у колекції. Рис. 6.11. Інтерфейс Ітератора
При наявності такого інтерфейсу ми можемо реалізувати ітератори для будь-яких видів колекцій об’єктів: масивів, списків, хеш-таблиць. Припустимо, ми хочемо реалізувати Ітератор для колекції Array з нашого приклада. Реалізація буде виглядати так (рис. 6.12): 1. Щоб додати Ітератор у DіnerMenu, спочатку необхідно визначити інтерфейс Іterator. 2. Необхідно реалізувати конкретний Ітератор для колекції DіnerMenu. 3. Необхідно лише додати один метод, що створює об’єкт DіnerMenuІterator і повертає його клієнту.
Рис. 6.12. Розширення інтерфейсу Ітератора
У результаті одержуємо: - реалізації надійно інкапсульовані. Клієнтський код не знає, як класи меню зберігають свої колекції елементів; - досить одного циклу, що поліморфно обробляє елементи будь-якої колекції, який реалізує Іterator; - клієнтський код використовує інтерфейс (Іterator); - інтерфейси двох класів меню тепер цілком збігаються. Загальна структура паттерна Ітератор представлена на рис. 6.13.
Рис. 6.13. Діаграма класів паттерна
Поиск по сайту: |
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Студалл.Орг (0.019 сек.) |