
- •Міністерство освіти та науки України в.В. Литвин, н.Б. Шаховська Проектування інформаційних систем
- •Передмова наукового редактора серії підручників «комп’ютинґ»
- •1.1. Складність програмного забезпечення
- •1.2. Структура складних систем
- •1.2.1. Приклади складних систем
- •1.2.2. П'ять ознак складної системи
- •1.2.3. Організована і неорганізована складність
- •1.3. Методи подолання складності
- •1.3.1. Роль декомпозиції
- •1.3.3. Роль абстракції
- •1.3.4. Роль ієрархії
- •1.4. Про проектування складних систем
- •1.4.1. Інженерна справа як наука і мистецтво
- •1.4.2. Сенс проектування
- •4. Методи подолання складності.
- •2.1. Базові означення
- •2.2. Методи проектування інформаційних систем
- •2.3. Види інформаційних систем
- •2.4. Рівні моделей даних
- •3. Види інформаційних систем.
- •3.1. Методологія процедурно-орієнтованого програмування
- •3.2. Методологія об'єктно-орієнтованого програмування
- •3.3. Методологія об'єктно-орієнтованого аналізу і проектування
- •3.4. Методологія системного аналізу і системного моделювання
- •4.1. Передісторія. Математичні основи
- •4.1.1. Теорія множин
- •4.1.2. Теорія графів
- •4.1.3. Семантичні мережі
- •4.2. Діаграми структурного системного аналізу
- •4.3. Основні етапи розвитку uml
- •3. Семантичні мережі.
- •5.1. Принципи структурного підходу до проектування
- •5.2. Структурний аналіз
- •5.3. Структурне проектування
- •5.4. Методологія структурного аналізу
- •5.5. Інструментальні засоби структурного аналізу та проектування
- •6.1. Основні елементи
- •6.2. Типи зв’язків
- •6.3. Техніка побудови
- •6.4. Діаграма бізнес – функцій
- •6.4.1. Призначення діаграми бізнес-функцій
- •6.4.2. Основні елементи
- •7.1. Призначення діаграм потоків даних та основні елементи
- •7.1.1. Зовнішні сутності
- •7.1.2. Процеси
- •7.1.3. Накопичувачі даних
- •7.1.4. Потоки даних
- •7.2. Методологія побудови dfd.
- •8.1. Діаграма «сутність-зв’язок»
- •8.2. Діаграма атрибутів
- •8.3. Діаграма категоризації
- •8.4. Обмеження діаграм сутність-зв’язок
- •8.5. Методологія idef1
- •9.1. Основні елементи
- •9.2. Типи керуючих потоків
- •9.3. Принципи побудови
- •10.1. Структурні карти Константайна
- •10.2. Структурні карти Джексона
- •11.1. Призначення case-технологій
- •11.2. Інструментальний засіб bPwin
- •11.2.4. Інші діаграми bpWin
- •11.2.5. Моделі as is і to be
- •11.3.1. Основні властивості
- •11.3.2. Стандарт idef1x
- •11.4. Програмний засіб Visio
- •12.1. Системний аналіз області наукових досліджень
- •12.1.1. Аналіз предметної області
- •12.2. Системний аналіз біржі праці
- •12.2.1. Дерево цілей
- •12.2.2. Опис об’єктів предметної області
- •12.2.3. Концептуальна модель
- •14.1. Еволюція об'єктної моделі
- •14.1.1. Основні положення об'єктної моделі
- •14.2. Складові частини об'єктного підходу
- •14.2.1. Парадигми програмування
- •14.2.2. Абстрагування
- •14.2.3. Інкапсуляція
- •14.2.4. Модульність
- •14.2.5. Ієрархія
- •14.2.7. Паралелізм
- •14.2.8. Збереженість
- •14.3. Застосування об'єктної моделі
- •14.3.1. Переваги об'єктної моделі
- •14.3.2. Використання об'єктного підходу
- •14.3.3. Відкриті питання
- •15.1. Природа об'єкта
- •15.1.1. Що є й що не є об'єктом?
- •15.1.2. Стан
- •15.1.3. Поведінка
- •15.1.4. Ідентичність
- •Void drag(DisplayItem I); // Небезпечно
- •15.2. Відношення між об'єктами
- •15.2.1. Типи відношень
- •15.2.2. Зв'язки
- •15.2.3. Агрегація
- •15.3. Природа класів
- •15.3.1. Що таке клас?
- •15.3.2. Інтерфейс і реалізація
- •15.3.3. Життєвий цикл класу
- •15.4. Відношення між класами
- •15.4.1. Типи відношень
- •15.4.2. Асоціація
- •15.4.3. Успадкування
- •15.4.4. Агрегація
- •15.4.5. Використання
- •15.4.6. Інсталювання (Параметризація)
- •15.4.6. Метакласи
- •15.5. Взаємозв'язок класів і об'єктів
- •15.5.1. Відношення між класами й об'єктами
- •15.5.2. Роль класів і об'єктів в аналізі й проектуванні
- •16.1. Важливість правильної класифікації
- •16.1.1. Класифікація й об’єктно-орієнтовне проектування
- •16.1.2. Труднощі класифікації
- •16.2. Ідентифікація класів і об'єктів
- •16.2.1. Класичний і сучасний підходи
- •16.2.2. Об’єктно-орієнтований аналіз
- •16.3. Ключові абстракції й механізми
- •16.3.1. Ключові абстракції
- •16.3.2. Ідентифікація механізмів
- •17.1. Призначення мови uml
- •17.2. Загальна структура мови uml
- •17.3. Пакети в мові uml
- •17.4. Основні пакети мета-моделі мови uml
- •17.5. Специфіка опису мета-моделі мови uml
- •17.6. Особливості зображення діаграм мови uml
- •18.1. Варіант використання
- •18.2. Актори
- •18.3. Інтерфейси
- •18.4. Примітки
- •18.5. Відношення на діаграмі варіантів використання
- •18.5.1. Відношення асоціації
- •13.5.2. Відношення розширення
- •18.5.3. Відношення узагальнення
- •18.5.4. Відношення включення
- •18.6. Приклад побудови діаграми варіантів використання
- •18.7. Рекомендації з розроблення діаграм варіантів використання
- •19.1. Клас
- •19.1.1. Ім'я класу
- •19.1.2. Атрибути класу
- •19.1.3. Операція
- •19.2. Відношення між класами
- •19.2.1. Відношення залежності
- •19.2.2. Відношення асоціації
- •19.2.3. Відношення агрегації
- •19.2.4. Відношення композиції
- •19.2.5. Відношення узагальнення
- •19.3. Інтерфейси
- •19.5. Шаблони або параметризовані класи
- •19.6. Рекомендації з побудови діаграми класів
- •20.1. Автомати
- •20.2. Стан
- •20.2.1. Ім'я стану
- •20.2.2. Список внутрішніх дій
- •20.2.3. Початковий стан
- •20.2.4. Кінцевий стан
- •20.3. Перехід
- •20.3.2. Сторожова умова
- •20.3.3.Вираз дії
- •15.4. Складений стан і підстан
- •20.4.1. Послідовні підстани
- •20.4.2. Паралельні підстани
- •15.5. Історичний стан
- •20.6. Складні переходи
- •15.6.1. Переходи між паралельними станами
- •20.6.2. Переходи між складеними станами
- •20.6.3. Синхронізуючі стани
- •20.7. Рекомендації з побудови діаграм станів
- •21.1. Стан дії
- •21.2. Переходи
- •21.5. Рекомендації до побудови діаграм діяльності
- •22.1.1. Лінія життя об'єкта
- •22.1.2. Фокус керування
- •22.2. Повідомлення
- •22.2.1. Розгалуження потоку керування
- •22.2.2. Стереотипи повідомлень
- •22.2.3. Тимчасові обмеження на діаграмах послідовності
- •22.2.4. Коментарі або примітки
- •22.3. Приклад побудови діаграми послідовності
- •22.4. Рекомендації з побудови діаграм послідовності
- •23.1. Кооперація
- •23.2.1. Мультиоб'єкт
- •23.2.2. Активний об'єкт
- •23.2.3. Складений об'єкт
- •23.3. Зв'язки
- •23.3.1. Стереотипи зв'язків
- •23.4. Повідомлення
- •23.4.1. Формат запису повідомлень
- •23.5. Приклад побудови діаграми кооперації
- •23.6. Рекомендації з побудови діаграм кооперації
- •24.1. Компоненти
- •24.1.1. Ім'я компоненту
- •24.1.2. Види компонент
- •24.2. Інтерфейси
- •24.3. Залежності
- •24.4. Рекомендації з побудови діаграми компонент
- •25.1. Вузол
- •25.2. З'єднання
- •25.3. Рекомендації з побудови діаграми розгортання
- •26.1. Загальна характеристика case-засобу Rational Rose
- •26.2. Особливості робочого інтерфейсу Rational Rose
- •26.1.1. Головне меню програми
- •26.1.2. Стандартна панель інструментів
- •26.1.3. Вікно браузера
- •26.1.4. Спеціальна панель інструментів
- •26.1.5. Вікно діаграми
- •26.1.6. Вікно документації
- •26.1.7. Вікно журналу
- •26.3. Початок роботи над проектом у середовищі Rational Rose
- •26.4. Розроблення діаграми варіантів використання в середовищі Rational Rose
- •26.5. Розроблення діаграми класів у середовищі Rational Rose
- •26.6. Розроблення діаграми станів у середовищі Rational Rose
- •26.7. Розроблення діаграми послідовності в середовищі Rational Rose
- •26.8. Розроблення діаграми кооперації в середовищі Rational Rose
- •26.9. Розроблення діаграми компонентів у середовищі Rational Rose
- •26.10. Розроблення діаграми розгортання в середовищі Rational Rose
14.2.4. Модульність
Поняття модульності. Поділ програми на модулі певною мірою дозволяє зменшити її складність. Однак набагато важливіше той факт, що всередині модульної програми створюється множина добре сформованих і документованих інтерфейсів. Ці інтерфейси необхідні для вичерпного розуміння програми в цілому. У деяких мовах програмування, наприклад в Smalltalk, модулів нема, і класи становлять єдину фізичну основу декомпозиції. В інших мовах, включаючи Object Pascal, C++, Ada, CLOS, модуль - це самостійна мовна конструкція. У цих мовах класи й об'єкти становлять логічну структуру системи, вони містяться в модулі, що утворюють фізичну структуру системи. Ця властивість стає особливо корисною, коли система складається з багатьох сотень класів.
Модульність - це поділ програми на фрагменти, які компілюються окремо, але можуть встановлювати зв'язки з іншими модулями. Зв'язок між модулями - це їхнє подання один про одного. У більшості мов, що підтримують принцип модульності як самостійну концепцію, інтерфейс модуля відділений від його реалізації. Таким чином, модульність і інкапсуляція дуже близькі між собою. У різних мовах програмування модульність підтримується по-різному. Наприклад, в C++ модулями є роздільно компілюючі файли. Для C/C++ традиційним є зберігання інтерфейсної частини модуля в окремому файлі з розширенням .h (так звані файли-заголовки). Реалізація, тобто текст модуля, зберігається у файлах з розширенням .с (у програмах на C++ часто використовуються розширення .сс, .ср і .срр). Зв'язок між файлами оголошується директивою макропроцесора #include. Такий підхід не є строгою вимогою самої мови. У мові Object Pascal принцип модульності формалізований трохи суворіше. У цій мові визначений особливий синтаксис для інтерфейсної частини й реалізації модуля (unit). Мова Ada іде ще на крок далі: модуль (названий package) також має дві частини - специфікацію й тіло. Але, на відміну від Object Pascal, допускається роздільне визначення зв'язків з модулями для специфікації й тіла пакету. Таким чином, допускається, щоб тіло модуля мало зв'язки з модулями, які невидимі для його специфікації.
Правильний поділ програми на модулі є майже таким ж складним завданням, як вибір правильного набору абстракцій. Оскільки на початку роботи над проектом рішення можуть бути незрозумілими, декомпозиція на модулі може викликати ускладнення. Для добре відомих програм (наприклад, створення компіляторів) цей процес можна стандартизувати, але для нових завдань (військові системи або керування космічними апаратами) завдання може бути дуже важким.
Модулі виконують роль фізичних контейнерів, в яких містяться класи і об'єкти під час логічного проектування системи. Така ж ситуація виникає в проектувальників бортових комп'ютерів. Логіка електронного устаткування може бути побудована на основі елементарних схем типу НЕ, І-НЕ, АБО-НЕ, але можна об'єднати такі схеми в стандартні інтегральні схеми (модулі), наприклад, серій 7400, 7402 або 7404.
Для невеликих завдань припустимий опис всіх класів і об'єктів в одному модулі. Однак для більшості програм (крім самих тривіальних) кращим рішенням буде згрупувати в окремий модуль логічно зв'язані класи й об'єкти, залишивши відкритими ті елементи, які зовсім необхідно бачити іншим модулям. Такий спосіб розбивання на модулі гарний, але його можна довести до абсурду. Розглянемо, наприклад, завдання, що виконується на багатопроцесорному устаткуванні й вимагає для координації своєї роботи механізм передачі повідомлень. У великих системах цілком звичайним є наявність декількох сотень і навіть тисяч видів повідомлень. Було б наївним визначати кожний клас повідомлення в окремому модулі. При цьому не тільки виникають труднощі з документуванням, але навіть просто пошук потрібних фрагментів опису стає надзвичайно важким для користувача. При внесенні в проект змін буде потрібно модифікувати й перекомпілювати сотні модулів. Цей приклад показує, що приховання інформації має й зворотню сторону. Поділ програми на модулі безсистемним чином є іноді набагато гіршим, ніж відсутність модульності взагалі.
У традиційному структурному проектуванні модульність - це мистецтво розкладати підпрограми за групами так, щоб в одну групу попадали підпрограми, що використовують одна одну або разом змінюються. В об’єктно-орієнтованому програмуванні ситуація трохи інша: необхідно фізично розділити класи й об'єкти, що становлять логічну структуру проекту.
На основі наявного досвіду можна перелічити прийоми й правила, які дозволяють складати модулі із класів та об'єктів найефективнішим чином. Кінцевою метою декомпозиції програми на модулі є зниження витрат на програмування за рахунок незалежного розроблення й тестування. Структура модуля повинна бути досить простою для сприйняття; реалізація кожного модуля не повинна залежати від реалізації інших модулів; повинні бути вжиті заходи для полегшення процесу внесення змін там, де вони найнеобхідніші. На практиці перекомпіляція тіла модуля не є трудомісткою операцією: заново компілюється модуль, і програма перекомпонується. Перекомпіляція інтерфейсної частини модуля, навпаки, трудомісткіша. У строго типізованих мовах доводиться перекомпілювати інтерфейс і тіло самого зміненого модуля, потім всі модулі, пов'язані з даним, модулі, пов'язані з ними, і так далі за ланцюжком. У підсумку для дуже великих програм може бути потрібно багато часу на перекомпіляцію (якщо тільки середовище розроблення не підтримує фрагментарну компіляцію), що явно небажано. Тому варто прагнути до того, щоб інтерфейсна частина модулів була за можливістю вузькою (у межах забезпечення необхідних зв'язків). Наш стиль програмування вимагає сховати все, що тільки можливо, у реалізації модуля. Поступовий перенос описів з реалізації в інтерфейсну частину набагато небезпечніший, ніж "вичищення" надлишкового інтерфейсного коду.
Таким чином, програміст повинен знаходити баланс між двома протилежними тенденціями: прагненням сховати інформацію й необхідністю забезпечення видимість тих або інших абстракцій у декількох модулях. Особливості системи, піддані змінам, варто приховувати в окремих модулях; у якості міжмодульності можна використовувати тільки ті елементи, ймовірність зміни яких мала. Всі структури даних повинні бути відображені в модулі; доступ до них буде можливий для всіх процедур цього модуля й закритий для всіх інших. Доступ до даних модуля повинен здійснюватися тільки через процедури цього модуля. Інакше кажучи, варто прагнути побудувати модулі так, щоб об'єднати логічно зв'язані абстракції й мінімізувати взаємні зв'язки між модулями. Виходячи із цього, приведемо визначення модульності:
Модульність - це властивість системи, що була розкладена на внутрішньо зв'язані, але слабко зв'язані між собою модулі.
Таким чином, принципи абстрагування, інкапсуляції й модульності є взаємодоповнюючими. Об'єкт логічно визначає межі певної абстракції, а інкапсуляція й модульність роблять їх фізично непорушними.
У процесі поділу системи на модулі можуть бути корисними два правила. По-перше, оскільки модулі служать в якості елементарних і неподільних блоків програми, які можуть використовуватися в системі повторно, розподіл класів і об'єктів за модулями повинен це враховувати. По-друге, багато компіляторів створюють окремий сегмент коду для кожного модуля. Тому можуть з'явитися обмеження на розмір модуля. Динаміка викликів підпрограм і розташування описів всередині модулів може сильно вплинути на локальність посилань і на керування сторінками віртуальної пам'яті. При поганому розбиванні процедур за модулями частішають взаємні виклики між сегментами, що приводить до втрати ефективності кеш-пам'яті й частій зміні сторінок.
На вибір розбивання на модулі можуть впливати й деякі зовнішні обставини. Під час колективного розроблення програм розподіл роботи здійснюється, як правило, за модульним принципом й правильний поділ проекту мінімізує зв'язки між учасниками. При цьому досвідченіші програмісти звичайно відповідають за інтерфейс модулів, а менш досвідчені - за реалізацію. На більшому рівні такі ж співвідношення справедливі для відношень між субпідрядниками. Абстракції можна розподілити так, щоб швидко встановити інтерфейси модулів за згодою між компаніями, що беруть участь у роботі. Зміни в інтерфейсі викликають багато розмов і сперечань, не говорячи вже про величезну витрату паперу, - всі ці фактори роблять інтерфейс вкрай консервативним. Що стосується документування проекту, то він будується, як правило, також за модульним принципом - модуль служить одиницею опису й адміністрування. Десять модулів замість одного потребують у десять разів більше описів, і тому, на жаль, іноді вимоги з документування впливає на декомпозицію проекту (у більшості випадків негативно). Можуть позначатися й вимоги таємності: частина коду може бути несекретною, а інша - секретною; остання тоді виконується у вигляді окремого модуля (модулів).
Звести разом настільки суперечливі вимоги досить важко, але головне усвідомити: побудова класів і об'єктів у проекті й організація модульної структури - незалежні дії. Процес побудови класів і об'єктів становить частину процесу логічного проектування системи, а розподіл на модулі - етап фізичного проектування. Зрозуміло, іноді неможливо завершити логічне проектування системи, не завершивши фізичного проектування, і навпаки. Два цих процеси виконуються ітераційно.
Приклади модульності. Подивимося, як реалізується модульність у гідропонній городній системі. Припустимо, замість закупівлі спеціалізованого апаратного забезпечення, вирішено використовувати стандартну робочу станцію із графічним інтерфейсом користувача GUI (Graphical User Interface). За допомогою робочої станції оператор може формувати нові плани вирощування, модифікувати наявні плани й спостерігати за їхнім виконанням. Тому що абстракція плану вирощування - одна із ключових. Створимо модуль, що містить всі ці операції стосовно плану вирощування. На C++ нам буде потрібний приблизно такий файл-заголовок (нехай він називається gplan.h).
// gplan.h
#ifndef _GPLAN_H
#define _GPLAN_H 1
#include "gtypes.h"
#include "except.h"
#include "actions.h"
class GrowingPlan ...
class FruitGrowingPlan ...
class GrainGrowingPlan ...
#endif
Тут ми імпортуємо у файл три інших заголовних файли з визначенням інтерфейсів, на які будемо посилатися: gtypes.h, except .h і actions.h. Власне код класів ми помістимо в модуль реалізації, у файл із ім'ям gplan.cpp.
Ми могли б також зібрати в один модуль всі програми, що відносяться до діалогових вікон, які є специфічними для цієї програми. Цей модуль напевно буде залежати від класів, оголошених в gplan.h, і від інших файлів-заголовків з описом класів GUI.
Ймовірно, буде багато інших модулів, що імпортують інтерфейси нижчого рівня. Нарешті ми доберемося до головної функції - точки запуску нашої програми операційною системою. При об’єктно-орієнтованому проектуванні це швидше за все буде сама малозначна й нецікава частина системи, у той час, як у традиційному структурному підході головна функція - це нарізний камінь, що тримає все спорудження. Ми думаємо, що об’єктно-орієнтований підхід природніший, оскільки на практиці програмні системи пропонують деякий набір послуг. Зводити їх до однієї функції можна, але антиприродно... Справжні системи не мають верхнього рівня.