Тема: cтворення й використання бібліотек і модулів мовою Java. Застосовання методу функціональної декомпозиції задачі.
Мета:
Після вивчення теми учень:
Обладнання: комп’ютери зі встановленими ОС та середовищем Netbeans програмування мовою Java.
Структура уроку
Хід уроку
1. Організаційний момент
Вітання з класом. Перевірка присутності і готовності учнів до уроку. Перевірка виконання домашнього завдання.
2. Актуалізація опорних знань
Принцип модульності у програмуванні формулюють як вимогу подати програму у виді сукупності модулів:
розмір модуля повинний бути обмежений;
модуль має виконувати логічно цілісну і завершену дію;
модуль має бути по можливості параметричним, тобто усі змінювані характеристики виконуваної дії потрібно передавати через параметри;
вхідні параметри і результат модуля бажано передавати не через глобальні змінні, а через формальні параметри і результат функції.
Функція — логічно самостійна частина програми з назвою (ідентифікатором), за допомогою якої здійснюють виклик функції. Функція може мати список аргументів (параметрів), які передають їй для використання. Цей список параметрів (аргументів) записують у круглих дужках ( ) одразу після назви через кому. При відсутності такого списку функцію називають функцією без параметрів. Може повертати (не обов'язково) деяке значення. У цьому випадку в описі функції перед її назвою вказують тип значення, яке повертають. Інакше замість назви типу використовують слово void.
Загальна форма опису функції
тип назва_функції (список_параметрів) // прототип функції { … // тіло функції return вираз; // тіло функції }
Тіло функції — вказівки функції, записані між фігурними дужками { } після назви.
Побічний ефект функції — будь-яка зміна функцією стану програмного середовища, крім повернення результату (зміна значень глобальних змінних, виділення й звільнення пам'яті, введення-виведення тощо).
Теоретично найправильнішим вважають використання функцій без побічних ефектів. Хоча на практиці доводиться використати функції з побічним ефектом, наприклад, для забезпечення введення-виведення й відображення результатів роботи програми.
3. Вивчення нового матеріалу
Примітка. Набуття практичнх навичок щодо створення і використання бібліотек та модулів у ході виконання дій, описаних у прикладах 1-3, можна здіснити при першому прочитанні опису або після нього — на розсуд вчителя. Але у першому випадку інструктаж з ТБ необхіно провести до виконання цих дій.
Для прискорення процесу розробки програми використовують бібліотеки й фреймворки.
Бібліотека Java — файл jar (Java ARchive), стиснутий у форматі zip і призначений для зберігання відкомпільованих класів — файлів з типом class.
Файл JAR містить теку META-INF, що у свою чергу містить файл MANIFEST.MF. Цей файл дозволяє розширити функціональність: крім звичайного набору класів, файл JAR може виконувати функції електронного підпису, підтримки версійності, підключення бібліотек до архіву, визначення класу для запуску програми. Опис усіх цих можливостей подано у документі Working with Manifest Files: The Basics.
Фреймворк (анґліською framework — каркас, структура) — програмна платформа, що визначає архітектуру побудови програми та полегшує розробку та об'єднання різних складових програмного проекту.
Відмінність між бібліотекою та фреймворком полягає у такому:
бібліотеку використовують як набір підпрограм близької функціональності без впливу на архітектуру програмного продукту і без накладання на неї обмежень. А фреймворк визначає правила побудови архітектури програми, задаючи на початковому етапі розробки поведінку як усталено;
фреймворк може взаємодіяти з великою кількістю різних за тематикою бібліотек;
при зверненні до бібліотеки керування отримує один із методів класу з бібліотеки. У фреймворку код користувача може втілювати конкретну поведінку, вбудовану у загальніший абстрактний код фреймворку. При цьому фреймворк викликає функції класу коду користувача.
Створити проєкт бібліотеки. Для цього зробити таке:
замінити код Pair.java таким.
package pair; public class Pair { int x, y; public Pair(int x, int y) {this.x = x; this.y = y;} public int s() {return this.x + this.y;} }
Створити проєкт, що буде використовувати бібліотеку: проєкт work містить однойменний пакунок work, що містить клас Work з таким кодом,
package work; import pair.Pair; // імпорт класу Pair з бібліотеки public class Work { public static void main(String args[]) { Pair p=new Pair(2,-7); System.out.println(p.s()); } }
Діі по створенню аналогічні описаним у попередньому пункті. Але замість Java Class Library потрібно вибрати Java Аpplication.
Отримати такий вигляд вікна середовища програмування.
Міткою виділено вказівку імпорту класу Pair пакунку pair, бо досі не приєднано бібліотеку з цим класом до проєкту work.
Скомпілювати бібліотеку — створити файл JAR:
або натиснути клавішу F11,
або використати вказівку меню Run / Build Project.
Пересвідчитися у появі файлу pair.jar у теці dist проєкту бібліотеки.
Приєднати бібліотеку до проєкту, що використовує цю бібліотеку:
у контексному меню бібліотек Libraries вибрати Add JAR/Folder… (Додати JAR/Теку);
у вікні діалогу Add JAR/Folder вказати файл pair.jar теки dist проєкту бібліотеки і натиснути кнопку Open.
Запустити проєкт work на виконання після зникнення мітки біля вказівку імпорту класу Pair у коді Work.java:
Пересвідчитися у правильній роботі програми — виведенні у консоль числа -5.
Примітка. В інших середовищах програмування цей процес може мати інший вигляд. Наприклад, в Eclipse — через експорт, а в IDEA — через створення артефактів.
Приклад 2. Cтворення і під'єднання бібліотек вказівками терміналу
Створити файли з кодами бібліотек класів.
pair/Pair.java
package pair; public class Pair { int x, y; public Pair (int x, int y) {this.x = x; this.y = y;} public int s() {return this.x + this.y;} }trio/Trio.java
package trio; public class Trio { int x, y, z; public Trio (int x, int y, int z) {this.x = x; this.y = y; this.z = z;} public int s() {return this.x + this.y + this.z;} }
Створити файл програми, що буде використовувати бібліотеки.
Work.java
package work; import pair.Pair; import trio.Trio; public class Work { public static void main(String args[]) { Pair p = new Pair (2,-7); Trio t = new Trio (2,-7, 3); System.out.println(p.s()+" "+t.s()); } }
Скомпілювати бібліотеки — створити файли з типом class, виконавши такі вказівки терміналу,
javac -d classes pair/Pair.java javac -d classes trio/Trio.javaі отримати таке піддерево файлової системи.
├─ classes │ │ │ ├─ pair │ │ └─ Pair.class │ │ │ └─ trio │ └─ Trio.class │ ├─ pair │ └─ Pair.java │ ├─ trio │ └─ Trio.java │ └─ work └─ Work.java
У кожній вказівці після запису -d вказано шлях до теки, куди буде записан компіляції. Цю теку та її наповнення буде створено внаслідок виконання вказівки.
Завершити формування бібліотек — створити (власноруч!) теку jars для збереження бібліотек у форматі jar. Після цього виконати такі вказівки терміналу
jar cf jars/pair.jar classes/pair/Pair.class jar cf jars/trio.jar classes/trio/Trio.classі отримати таке піддерево файлової системи.
├─ classes │ ├─ pair │ │ └─ Pair.class │ └─ trio │ └─ Trio.class ├─ jars │ ├─ pair.jar │ └─ trio.jar ├─ pair │ └─ Pair.java ├─ trio │ └─ Trio.java └─ work └─ Work.java
У вказівках запис cf означає створення файлу (від анґліського to create file), після чого вказано шлях до теки призначення. Ця тека має існувати на час виконання вказівки, інакше буде помилка виконання.
Скомпілювати файл програми, що буде використовувати бібліотеки, виконавши таку вказівки терміналу.
javac -cp "./:jars" work/Work.java
У цій вказівці запис -cp (можна писати -classpath) вказує, що наступний рядок містить шляхи до усіх тек, з яких буде взято файли для компіляції. Шляхи до тек розділяють двокрапкою ":" при ОС Linux, крапкою з комою ";" — при ОС Windows. Поточну теку позначають "./" — крапкою з похилою рискою. У результаті виконання вказівки у теку work буде записано файл Work.class — результат компіляції.
Запустити програму на виконання такою вказівкою терміналу.
java -cp "./:jars" work/Work
Пересвідчитися у правильній роботі програми, яка має вивести одним рядком два числа.
-5 -2
Пакунок — окрема частина програми зі своїм простором назв. Пакунку відповідає однойменна тека (каталог, директорія), що містить класи. Кількість класів у пакунку не обмежено.
Основні вбудовані пакунки Java:
Детально ознайомитися з цими пакунками можна за англомовними ресурсами:
JavaDoc і
exampledepot.com.
Можливості Java не обмежуються лише стандартними засобами. Є ще багато широковживаних бібліотек, серед яких можна назвати такі:
Рівні інкапсуляції — приховування деталей інформації про роботу класів від об'єктів, що їх використовують або надсилають їм повідомлення — у мові Java такі:
Переваги від використання модулів. Без модуля всі ресурси розташовують на кореневому рівні проєкту та вручну визначають, які ресурси належать до яких частин програми. Завдяки модулям можливо відправляти необхідні зображення й файли XML разом із модулем, який їх потребує. Це істотно полегшує керування проєктами.
Дескриптор (файл опису) модуля визначає такі властивості модуля:
Назвою модуля може бути довільна послідовність алфавітно-цифрових символів та знаків підкреслення. Точки дозволені, тире — ні. Бажано, щоб назва модуля відповідала назві, з якої починаються назви пакунків цього модуля.
Потрібно вказувати модифікатор доступу public для всіх пакунків, які хочемо використати, бо як усталено усі пакунки модуля мають модифікатор доступу private.
Те саме стосується й відображення (reflection): як усталено не можна використовувати відображення класів, імпортованих з іншого модуля.
Типи модулів
Модулі програми — це модулі, названі та визначені у скомпільованому файлі module-info.class, включеному в зібраний JAR. Їх створюють, коли вирішують використовувати модулі.
Автоматичні модулі — це модулі, які під'єднують, додаючи наявні файли JAR у шляху до модуля. Назва модуля буде похідною від назви JAR. Автоматичні модулі мають повний доступ для читання до кожного іншого модуля, завантаженого шляхом.
Безіменний модуль — у випадку, коли клас або JAR завантажують у шлях до класу, але не в шлях до модуля. Клас буде автоматично додано до безіменного модуля. Такий модуль використовують для підтримки зворотної сумісності з раніше написаним кодом Java.
Розповсюдження модулів здійснюють:
Оголошення модуля здійснюють у дескрипторі модуля — файлі module-info.java, розташованому у корені пакунків. Опис модуля починають службовим словом module, за яким слідує назва модуля і вказівки опису модуля у фігурних дужках.
module moduleName { // опис модуля }
Вказівки опису модуля (у коді після них записують крапкою з комою)
requires module.name — вказує на модуль з назвою name, від якого залежить поточний модуль під час виконання і під час компіляції;
requires static module.name — вказує на модуль з назвою name, від якого залежить поточний модуль лише під час компіляції — використовують у випадку, коли модуль name користувач поточного модуля не буде використовувати явно;
requires transitive module.name — вказує транзитивну залежність від модуля з назвою name: якщо модуль name транзитивно залежить від модуля x, то поточний модуль також матиме доступ до x;
exports name — відкриває доступ звідусіль до пакунку name поточного модуля.
Як усталено модуль не надає іншим модулям жодного свого API (англійською Application Programming Interface — прикладни́й програмний інтерфейс — набір визначень підпрограм, протоколів взаємодії та засобів для створення програмного забезпечення). Така сильна інкапсуляція була одним із ключових мотивів для створення модульної системи. Код стає значно безпечнішим, але вимагає явно відкрити API для використання.
exports name to x, y, …, z — відкриває доступ до пакунку name поточного модуля лише з модулів x, y, …, z;
uses Name — вказує, що поточний модуль використовує послугу Name — інтерфейс або абстрактний клас служби.
Існує різниця між вказівками requires і uses. Може знадобитися модуль, який надає послугу, яку ми хочемо використовувати, але ця послуга реалізує інтерфейс з однієї зі своїх транзитивних залежностей. Замість того, щоб змусити модуль вимагати всіх транзитивних залежностей про всяк випадок, використовують вказівку uses, щоб додати лише необхідний інтерфейс до шляху модуля.
provides Name with Impl — відкриває послугу Name — інтерфейс або абстрактний клас служби поточного модуля — для використання або розширення у класі Impl.
open module name{} — відкриває весь модуль name для повного відображення.
Інкапсуляція була рушійною мотивацією для розробки модульної системи Java. До Java 9 можна було використовувати відображення для перевірки кожного типу та члена пакунку, навіть приватних. Нічого не було по-справжньому інкапсульовано.
Java 9 забезпечило сильну інкапсуляцію: тепер потрібно явно надати дозвіл іншим модулям відображати класи за допомогою вказівки open.
opens name — відкриває лише пакунок name, а не весь модуль, на доступ звідусіль;
opens name to x, y, …, z — відкриває лише пакунок name, а не весь модуль, на доступ з модулів x, y, …, z.
Наразі в Maven і Gradle додано підтримку модулів, тому немає потреби створювати проекти вручну. Однак корисно знати, як використовувати модулі з рядка вказівок.
Ми будемо використовувати командний рядок для нашого повного прикладу нижче, щоб допомогти зміцнити, як вся система працює в нашій свідомості.
Параметри командного рядка
Cтворити файл дескриптора модуля /home/chief/demo/a/module-info.java з кодом.
module a {}
Створити головний файл пакунку /home/chief/demo/a/b/Hello.java з таким кодом.
package b; public class Hello { public static void main(String[] args) { System.out.println("Привіт!"); } }
Cкомпілювати модуль, виконавши у терміналі таку вказівку:
/usr/lib/jvm/jdk-16.0.1/bin/javac -d mods/a demo/a/module-info.java demo/a/b/Hello.javaУ вказівці вказано шляхи до:
Шлях до javac може бути іншими при операційній системі Linux і буде іншим при інших операційних системах. Повний шлях до компілятора javac можна не записувати, якщо попередньо їх записати у вказівці додавання назви відповідної теки до змінної PATH.
export PATH=$PATH:/usr/lib/jvm/jdk-16.0.1/bin/
Інколи і цього робити при наявності лише одного компілятора мови Java.
Внаслідок виконання вказівки буде створено такі файли:
і виникне таке піддерево файлової системи:
├── demo │ └─ a │ ├─ b │ │ └─ Hello.java │ └─ module-info.java └── mods └─ a ├─ b │ └─ Hello.class └─ module-info.class
У разі відсутності теки mods чи вкладених у неї тек їх буде створено. Якщо замість назви mods буде вибрано demo, файли *.class буде записано у ті самі теки, у яких розташовано і відповідні файли *.java.
Виконати програму за допомогою такої вказівки:
/usr/lib/jvm/jdk-16.0.1/bin/java --module-path mods -m a/b.Hello
Примітка. Якщо на ПК розгорнуто кілька версій java, а версія java як усталено передує (має менший номер) версії, узгодженій з використаним компілятором, буде отримано повідомлення про помилку:
Unsupported major.minor version 60.0
Останні цифри можуть бути іншими залежно від номерів версій, що різняться.
Пересвідчитися у дієздатності модуля, помітивши виведення у консоль повідомлення: "Привіт!"
Створити теку jars для розташування створеного з модуля файлу jar (від англійського Java ARchive — Java-архів. Є ZIP-архівом, що містить частину програми мовою Java). Якщо такої теки не створити, наступні вказівки лише породжуватимуть повідомлення про помилку. Вилучивши згадку про таку теку з цих вказівок, отримаємо роботу у поточній теці.
Cтворити файл jar модуля, виконавши у терміналі таку вказівку (один рядок):
/usr/lib/jvm/jdk-16.0.1/bin/jar --create --file jars/d.jar --main-class b.Hello --module-version 1.0 -C mods/a module-info.class -C mods/a b/Hello.class
У цій вказівці використано такі аргументи:
-create вказує на створення нового файлу JAR;
--file вказує шлях до вихідного файлу (створеного файлу JAR). Теки для розташування вихідного файлу JAR повинні існувати на момент виконання вказівки;
-C повідомляє команді jar змінити теку і включити все, розташоване у цій теці.
Після виконання вказівки буде створено файл jars/d.jar, а піддререво файлової системи набуде такого вигляду:
├── demo │ └─ a │ ├─ b │ │ └─ Hello.java │ └─ module-info.java │ ├── jars │ └─ d.jar │ └── mods └─ a ├─ b │ └─ Hello.class └─ module-info.class
Виконати програму, запустивши файл jar на виконання за допомогою такої вказівки:
/usr/lib/jvm/jdk-16.0.1/bin/java -jar jars/d.jar
Пересвідчитися у дієздатності файлу jar, помітивши виведення у консоль повідомлення: "Привіт!"
Способи передавання параметра у функцію у більшості мов програмування такі:
за значенням (by value) — передають копії значень змінних. Інакше кажучи, зміна значень параметрів у тілі функції не змінює значень змінних, переданих у функцію ззовні при її виклику;
за посиланням (by reference) — передають посилання на змінну. Зміна у тілі функції значення параметра, переданого за посиланням, змінює значення параметра за межами функції.
Java завжди передає параметри за значенням. Мова йде і про примітиви, і про посилання об'єкти. Наприклад, у результаті виконання такого коду
class Rextester { public static void main(String args[]) { int a=1; System.out.print(a); change(a); System.out.print(a); } private static void change (int b) { b+=1; System.out.print(b); } }
буде виведено таке:
121
Але спроба діяти аналогічно з об'єктом (наприклад, з масивом) призводить до результату, що ніби суперечить тезі: "Java завжди передає параметри за значенням.". Наприклад, у результаті виконання такого коду:
class Rextester { public static void main(String args[]) { String[] abc = new String[] {"a", "b", "c"}; System.out.println(abc[0]+abc[1]+abc[2]); change(abc); System.out.println(abc[0]+abc[1]+abc[2]); } private static void change (String[] d) { d[0]="x"; d[1]="y"; d[2]="z"; System.out.println(d[0]+d[1]+d[2]); } }
буде виведено таке:
abc xyz xyz
Що ж відбулося? При виклику функції change у функцію було передано значення для створення копії вказівника на об'єкт (масив), а не самого об'єкта. Таким чином, у момент роботи функції існує два вказівника, які вказують на один і той самий об'єкт, який буде змінено у разі виклику функції.
Формальний параметр — це ідентифікатор (змінна) в описі параметрів функції. Наприклад, у прикладі вище — це d.
Область видимості формального параметра функції визначається межами тіла функції.
Фактичний параметр (аргумент) — це значення, передане у функцію для надання цього значення формальному параметру.
Декомпози́ція — метод вирішення проблем, що використовує структуру завдання і полягає у заміні вирішення одного великого завдання вирішенням послідовності менших завдань (нехай і взаємопов'язаних).
Декомпозиція розглядає досліджувану систему як складну, що містить з окремі взаємопов'язані підсистеми, які, у свою чергу, також можна поділити на частини. У якості системи можуть виступати не лише матеріальні об'єкти, а й процеси, явища і поняття. Вихідна система вважають нульовим рівнем. Після її поділу отримують підсистеми першого рівня. Розділення цих підсистем або деяких з них призводить до появи підсистем другого рівня і т. д. Таким чином виникає ієрархічна структура, що відображає процес декомпозиції і містить усі системи, що виникають у процесі її здійснення і зв'язки належності до її елементів.
Таку ієрархічну структуру можна зобразити у вигляді блок-схеми. Для її аналізу можна застосовувати теорію графів. Це дозволяє перейти від наочної графічної моделі до абстрактної математичної. Ієрархічну структуру подають деревом (графом без циклів — замкнутих маршрутів) з дугами (орієнтованими ребрами) і розташуванням вершин на певних рівнях, що визначають за кількістю переходів від кореня вихідної системи.
Глибина декомпозиції — кількість рівнів деталізації — визначається, виходячи з вимог щодо видимості й зручності сприйняття ієрархічної структури, її відповідності рівням знання фахівця. Зазвичай за нижній (елементарний) рівень підсистем беруть такий, на якому розташовують підсистеми, розуміння природи яких або їхній опис доступний виконавцю. Таким чином, ієрархічна структура декомпозиції завжди суб'єктивно орієнтована.
Велика кількість рівнів створює враження, що завдання складне. Велика кількість підсистем однієї системи (на одному рівні) спричиняє складність встановлення зв'язків між ними. Зазвичай виділяють 3-6 рівнів.
Наприклад, механічний привід містить колеса, вали, підшипники, двигун. Підшипники і двигун є складними за будовою і трудомісткими у проектуванні. Але як готові вироби для розробника приводу вони є елементарними частинами. Але при проектуванні й виробництві двигуна його потрібно розкласти на простіші підсистеми.
Будь-яке завдання, яке виконують програмісти, — це створення проектів на основі їхньої декомпозиції. Ієрархічна структура робіт (Work Breakdown Structure, WBS) — це інструмент, що дозволяє розбити проект на складові частини. Саме вона встановлює ієрархічно структурований розподіл робіт з реалізації проекту для всіх задіяних в ньому працівників.
У ході побудови WBS здійснюється послідовна декомпозиція проекту на підпроекти — пакети робіт різного рівня, пакети детальних робіт. Декомпозиція повинна бути коректною: елементи будь-якого рівня повинні бути необхідні й достатні для створення відповідного елемента верхнього рівня.
Ієрархічна структура робіт є переліком завдань проекту. Її можна подати графічно або у вигляді опису, що відображає вкладення робіт. Ієрархічна структура робіт організовує і визначає весь зміст проекту. Роботи, не включені у неї, не є роботами проекту.
Поширеною проблемою для управління проектами є нераціональний розмір пакетів робіт, якщо вони стають занадто великими для ефективного керування. Щоб забезпечити раціональний розмір пакетів робіт, необхідно дотримуватися такого.
Правила декомпозиції
Правило звітного періоду: тривалість кожного завдання не має перевищувати період часу між нарадами, присвяченими розгляду ходу проекту. Наприклад, якщо такі наради проводять щотижня, тривалість виконання кожного завдання не повинно перевищувати одного тижня.
Правило корисності: при дробленні завдання на дрібніші потрібно враховувати, що існують три причини, що зумовлюють доцільність такого поділу:
дрібнішу задачу, легше оцінити внаслідок меншої тривалості її виконання, а отже, меншою невизначеності;
дрібніші й конкретні завдання легше розподіляти між окремими виконавцями;
виконання дрібнішого завдання легше контролювати.
Якщо дроблення не відповідає цим вимогам, від нього потрібно відмовитися.
Ієрархічна структура робіт (WBS) створюють або згори донизу, або знизу догори, або використовують обидва підходи. Зазвичай застосовують «рухому хвилю» — чим віддаленіший за часом той чи інший елемент, тим менше глибина його декомпозиції.
У результаті побудови ієрархічної структури робіт потрібно врахувати всі цілі проекту і створити всі необхідні передумови для його успішного втілення.
Підстави для розбиття проекту:
Мистецтво декомпозиції проекту полягає в узгодженні основних структур проекту:
Ієрархічна структура робіт повинна відображати структуру створюваного об'єкта на верхньому рівні керування і надавати можливість переходити до структур, що характеризує специфічні роботи нижніх рівнів. Завдання нижнього рівня роботи входять до календарного плану робіт, за ними оцінюють час виконання проекту і витрати на проект. При цьому для кожного рівня потрібно передбачити процедури визначення відповідальних менеджерів і процедури вирішення конкретних ситуацій з урахуванням пріоритетів вищого рівня.
Набір робіт на нижньому рівні повинен бути необхідним і достатнім для виконання проекту, а декомпозиція виступає одним з найголовніших завдань керівника проекту. Реалізацію змісту проекту відстежують за ієрархічною структурою робіт (WBS), продукт проекту порівнюєть з вимогами до нього, тому WBS потрібно побудувати таким чином, щоб результатом проектних робіт стало створення заданого змісту продукту проекту.
Правила побудови ієрархічної структури робіт (WBS)
На основі попередньої інформації проводять послідовну декомпозиція робіт проекту. Її продовжують до тих пір, поки всі складові не буде визначено таким чином, щоб їх могли планувати, скласти для них бюджет і т.п.
Кожному елементу WBS надають унікальний ідентифікатор — WBS-код. Коди організовано відповідно до плану рахунків — системи відстеження витрат проекту за категоріями, що грунтується на плані рахунків організації і прийнятої в ній системі управлінського обліку.
Всі елементи WBS описують у словнику. Словник містить короткий опис кожного елемента, що входить в ієрархічну структуру робіт, тобто:
На основі WBS будують інші структурні моделі проекту:
Як правило, для успішного виконання завдання програміст (чи колектив програмістів) вимушений використати декомпозицію до того рівня деталізації, при якому кожну складову можна подати окремим класом чи функцією (процедурою).
На прикладах задач зі шкільного курсу інформатики користь від декомпозиції можна отримати лише на етапі розробки програми. Можливість паралельних (одночасних) обчислень призводить до можливості отримати користь на етапі виконання, підвищивши ефективність розв'язання за часом. Наприклад, у царині векторної алгебри, при використанні різнецевих схем для наближення рівнянь аеро- та гідродинаміки, при розпізнаванні образів. Останнє, крім цивільного застосування, має і військове. Тому надзвичайно актуальними є поняття й міркування, подані далі. Хоча з ними реально зіткнутися як розробник можна лише при роботі у провідних (у цірині інформаційних технологій) організаціях і корпораціях.
Розглянемо процес створення алгоритму і місце у ньому декомпозиції, яка особливо важлива для створення ефективних паралельних обчислень.
Декомпозиція. На цьому етапі початкову задачу аналізують щодо можливості й доцільності її розкладу й розпаралелювання. При можливості й доцільності задачу й пов’язані з нею дані розділяють підзадачі й структури даних.
Проектування обміну даними між задачами. На цьому етапі визначають зв’язки, необхідні для пересилання вхідних даних, проміжних результатів і вказівок для керування виконанням, обирають методи й алгоритми комунікацій.
Укрупнення (агрегування). Інколи підзадачі доцільно об’єднати у більші блоки, якщо це підвищує ефективність алгоритму і знижує трудоємкість розробки.
Планування обчислень. На цьому етапі розподіляють обчислювальні ресурси на виконання окремих підзадач. Основний критерій вибору — ефективне використання з мінімальними витратами часу на обмін даними.
Іноді декомпозиція поставленої задачі природним чином випливає з умови задачі і є очевидною,
іноді — ні. Чим менший розмір підзадач, отриманих у результаті декомпозиції, тим більша їхня кількість, тим гнучкішим можна зробити паралельний алгоритм і тим легше забезпечити рівномірне завантаження процесорів обчислювальної системи.
Розділяти (сегментувати) можна як обчислювальний алгоритм, так і дані.
Декомпозиція даних полягає у поділі даних з наступним відповідним поділом алгоритму їхнього опрацювання. Дані розбиваються на частини приблизно однакового розміру. З ними пов’язують операції їхнього опрацювання, з яких і формують підзадачі. Визначаються необхідні правила обміну даними. Перекриття частин, на які розбивають задачу, має бути мінімальним. Це дозволяє уникнути дублювання обчислень. Зазвичай спочатку аналізують структури даних (можливо й динамічні) найбільшого розміру або ті, до яких найчастіше звертаються.
Функціональна декомпозиція полягає у поділі алгоритму з наступним підбором схеми декомпозиції даних. Успіх (підвищення ефективності) в цьому випадку не завжди гарантовано, оскільки схема може вимагати багатьох додаткових пересилань даних. Але цей метод декомпозиції може виявитися корисним у випадку, якщо немає структур даних, які можна розпаралелити очевидним чином.
Розмір блоків програми може бути різним. Залежно від розміру блоків вказівок, які виконують паралельно (одночасно), розпаралелення алгоритму може мати різну «зернистість». Її виміром у найпростішому випадку є кількість операцій у блоці. Зазвичай виділяють три ступеня «зернистості» дрібнозернистий, середньоблоковий і великоблоковий:
Паралельність на рівні вказівок — до 20 вказівок на блок, у середньому 5 — найдрібнозернистіший. Кількість підзадач, що паралельно виконують — від однієї до кількох тисяч. Паралельність на рівні циклів. Переважно цикл містить до 500 вказівок. Якщо ітерації циклу незалежні, їх можна виконувати, наприклад, за допомогою конвеєра або на векторному процесорі. Усе це дрібнозернистий паралелізм.
Паралельність на рівні підзадач — середньоблоковий рівень при розмірі блоку до 2000 вказівок. Його втілити важче, бо важче врахувати можливі залежності. Вимоги до комунікацій менші, ніж у випадку паралелізму на рівні вказівок.
Паралельність на рівні програм (задач) — великоблоковий рівень. Він означає виконання незалежних програм на паралельних (різних) комп’ютерах. Такий паралелізм підтримують за допомогою операційної системи.
Обмін даними через спільні змінні використовують на рівнях дрібнозернистого й
середньоблокового паралелізму, а на великоблоковому — засобами передачі повідомлень.
Частини програми (підзадачі) можна виконувати паралельно (одночасно), якщо вони незалежні за даними, за керуванням і за введенням/виведенням:
незалежність за даними полягає у тому, що дані, які опрацьовують згідно з однією частиною програми, не модифікує інша її частина;
незалежність за керуванням полягає у тому, що послідовність виконання частин програми можна довільним чином визначити під час виконання програми. Наявність залежності визначає порядок виконання;
незалежність за ресурсами полягає у достатній кількості комп’ютерних ресурсів (об’єм пам’яті, кількість функціональних вузлів тощо);
залежність за виведенням полягає в одночасному записі значення в одну й ту саму змінну щонайменше двома підзадачами;
залежність за введенням/виведенням полягає в одночасному зверненні до одного й того самого файлу чи змінної різними підзадачами.
Історичний нарис. У 1974 р. на конгресі ІFІP Глушков виступив із доповіддю про рекурсивні ЕОМ, засновані на нових принципах організації обчислювальних систем (співавтори В.А.Мясніков, І.Б.Ігнатьєв, В.А.Торгашев). Він висловив думку про те, що лише розробка принципово нової не неймановської архітектури обчислювальних систем, дозволить вирішити проблему побудови супер-ЕОМ із необмеженим ростом продуктивності при нарощуванні апаратних засобів. Подальші дослідження показали, що повна і безкомпромісна реалізація принципів побудови рекурсивних ЕОМ і мозкоподібних структур при наявному тоді рівні електронної технології передчасна. Компромісні рішення було знайдено Глушковим і покладено в основу оригінальної структури високопродуктивної ЕОМ, названої макроконвеєром.
Глушков не зміг побачити створені за його ідеями макроконвеєрні ЕОМ ЄС-2701 і ЄС-1766, які не мали аналогів у світовій практиці (за оцінкою Державної комісії, яка приймала роботи). Це були найпотужніші обчислювальні системи на початку 1990-х років. Продуктивність ЄС 1766 при використанні повного комплекту процесорів (256 пристроїв) оцінювали два мільярди операцій на секунду. На жаль, ці потужні й конкурентноспроможні щодо кращих американських ЕОМ комп'ютери було випущено малою серією.
У період з 1990 по 1993 роки понад 90% експлуатованих у СРСР мейнфреймів ЄС ЕОМ було демонтовано й утилізовано з метою вилучення з них дорогоцінних металів (кілька десятків грамів золота і близько одного або декількох кілограмів срібла в одній ЕОМ).
4. Інструктаж з ТБ
5. Вироблення практичних навичок
Завдання 1. Здійснити дії, описані у прикладі 1: створення і під'єднання бібліотеки у середовищі Netbeans. Назви пакунків, класів та їхнє наповнення можуть бути іншими — на розсуд учня.
Завдання 2. Здійснити дії, описані у прикладі 2: створення і під'єднання бібліотеки вказівками терміналу. Назви пакунків, класів та їхнє наповнення можуть бути іншими — на розсуд учня.
Завдання 3. Здійснити дії, описані у прикладі 3: створення і використання модуля засобами вказівок терміналу. Назви пакунків, класів та їхнє наповнення можуть бути іншими — на розсуд учня.
Завдання 4. Створити програму й використовувані нею бібліотеки (по одній бібліотєці з одним класом для одного учня) для вирішення найпростіших для обчислювальної геометрії (методу координат) на площині.
Використати метод функціональної декомпозиції при роботі у групах до 8 учасників з двома підсумковими проєктами (завдання 7-8), виконавці яких спільно формулюють технічні вимоги до розв'язань решти. Програми й використовувані бібліотеки розташувати в різних теках.
6. Підбиття підсумків уроку
Виставлення оцінок.
7. Домашнє завдання
Вивчити матеріал уроку. При потребі доробити завдання.
Текст упорядкував Олександр Рудик.