1. Об'єкти та класи

Сьогодні ви дізнаєтеся трохи про те, як влаштовано типову програму мовою Java. І головна новина: кожна програма, написана мовою Java, складається з класів і об'єктів.

Що таке класи, ви вже знаєте, а що таке об'єкти?

Почну з аналогії. Уявіть, що ви хочете побудувати невеликий корабель. Спочатку потрібно створити креслення, потім віддати його на завод, де за цим кресленням виготовлять корабель. Або десяток. Та й узагалі, скільки завгодно кораблів. За одним кресленням будують десятки ідентичних кораблів — от що важливо.

У програмуванні мовою Java все так само.

Креслення

Програміст чимось схожий на проєктувальника. Тільки проєктувальник виконує креслення, а Java-програміст пише класи. Потім на основі креслень створюють деталі, а на основі класів — об'єкти.

Спочатку ми пишемо класи (робимо креслення), а відтак під час виконання програми на основі цих класів Java-машина створює об'єкти. Точнісінько так само, як кораблі споруджують на основі креслень.

Креслення одне, а кораблів може бути багато. Кораблі різні, у них різні назви, вони перевозять різні вантажі. Однак вони дуже схожі: це кораблі з ідентичною конструкцією та можуть виконувати подібні завдання.

А от інша аналогія…

Мурашник

Мурашник — це гарний приклад взаємодії об'єктів. У найпростішому мурашнику є три класи мурах: королева, солдати й робітники.

Кількість мурах кожного класу різна. Королева — одна на весь мурашник, солдатів — десятки, а робочих мурах — сотні. Три класи й сотні об'єктів. Мурахи взаємодіють одна з одною, з такими самими мурахами й мурахами інших класів за чітко визначеними правилами.

Це просто ідеальний приклад. У типовій програмі все так само. Є головний об'єкт, який створює об'єкти всіх інших класів. Об'єкти починають взаємодіяти один з одним та із «зовнішнім світом» програми. Усередині цих об'єктів жорстко запрограмовано їхню поведінку.

Обидва пояснення — це два боки однієї медалі. Істина — посередині. Перший приклад (про креслення й кораблі) демонструє зв'язок між класом і об'єктами цього класу. Аналогія дуже сильна. Другий приклад (про мурашник) показує зв'язок між об'єктами, що існують під час роботи програми, і написаними класами.

Спершу ви повинні створити класи для всіх наявних у програмі об'єктів, і до того ж описати взаємодію цих об'єктів. Саме так, але це легше, ніж здається.

У Java всі сутності під час роботи програми є об'єктами, а написання програми зводиться до опису різних способів взаємодії між ними. Об'єкти просто викликають методи один одного та передають у них потрібні дані.

Документація

А як дізнатися, які дані слід передавати в методи? Тут все вже придумано до вас.

Зазвичай кожний клас має опис, з якого зрозуміло, для чого цей клас створено. Так само і кожний публічний метод зазвичай має опис: що він робить і які дані слід у нього передавати.

Для використання класу потрібно в загальних рисах знати, що він робить. Крім того, потрібно точно знати, що робить кожен його метод. І зовсім необов'язково знати, як він це робить. Така собі чарівна паличка.

Погляньмо, наприклад, на код для копіювання файлу:

Копіювання файлу c:\data.txt у файл c:\result.txt
package com.javarush.lesson2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileCopy
{
   public static void main(String[] args) throws IOException
   {
      FileInputStream fileInputStream = new FileInputStream("c:\\data.txt");
      FileOutputStream fileOutputStream = new FileOutputStream("c:\\result.txt");

      while (fileInputStream.available() > 0)
      {
         int data = fileInputStream.read();
         fileOutputStream.write(data);
      }

      fileInputStream.close();
      fileOutputStream.close();
   }
}

Якщо прочитати цей код по рядках, можна приблизно здогадатися, що він робить. Щоправда тут потрібні досвід і практика. Отож через деякий час цей код вам буде здаватися знайомим і зрозумілим.


2. Проєктування програми

Проєктування програми — це справжнє мистецтво. Це і просто, і складно водночас. Просто, оскільки ніяких суворих законів немає: що не заборонено — те дозволено. Ну а складно з тієї самої причини: є дуже багато способів щось зробити, тому нелегко обрати найкращий.

Проєктувати програму — це як писати книгу. З одного боку, ви просто пишете літери, слова, речення. А з іншого — важливими є сюжет, характери героїв, внутрішні суперечності, конфлікти, стиль оповіді, інтрига тощо.

Головне — розуміти, для кого ви пишете код. А код ви пишете для інших програмістів.

Розробка будь-якого продукту — це внесення змін: додали тут, видалили там, переробили тут. І так маленькими ітераціями народжуються великі, величезні й гігантські проєкти.

Основна вимога до коду — він має бути зрозумілим іншим програмістам. Неправильний, але зрозумілий код можна виправити. Правильний і незрозумілий код покращити не вдасться. Його залишиться тільки викинути.

Отже, як писати хороший і зрозумілий код?

Для цього потрібно робити три речі:

  • Писати хороший і зрозумілий код усередині методів (найпростіше)
  • Вирішувати, які сутності мають бути в програмі
  • Правильно розбивати програму на логічні частини

Що ж стоїть за цими поняттями?

Писати хороший код усередині методів

Якщо ви хоча б на початковому рівні знаєте англійську, то, можливо, звернули увагу, як іноді легко читається код — ніби речення англійською мовою:

  • class Cat extends Pet —  клас Кіт розширює клас СвійськаТварина
  • while(stream.ready()) — поки потік готовий…
  • if (a<b) return a; else return b — якщо а менше за b, повернути а, інакше повернути b.

Так зробили навмисно. Java — одна з кількох мов, якими легко писати самодокументований код, тобто код, зрозумілий без коментарів. У хорошому коді на мові Java багато методів читаються просто як речення англійською мовою.

Ваше завдання під час написання коду — теж робити його якомога простішим і лаконічнішим. Просто думайте, наскільки легко читатиметься ваш код, і ви почнете рухатися в правильному напрямку.

У Java заведено писати легкочитний код. Бажано, щоб кожен метод цілком вміщався на екрані (довжина методу — 20–30 рядків). Це норма для всієї Java-спільноти. Якщо код можна поліпшити, його треба поліпшити.

Найкращий спосіб навчитися писати хороший код — постійна практика. Пишіть багато коду, аналізуйте чужий, просіть досвідченіших колег перевірити ваш код.

І пам'ятайте, що в той момент, коли ви скажете собі: «І так добре!», ваш розвиток зупиниться.

Вирішувати, які сутності мають бути в програмі

Вам потрібно писати код, зрозумілий іншим програмістам. Якщо 9 із 10 програмістів, проєктуючи програму, зроблять у ній класи A, B і С, то й вам також треба зробити у своїй програмі класи A, B, і C. Ваш код мають розуміти інші.

Відмінний, працюючий, швидкий нестандартний код — це поганий код.

Вам потрібно вивчати чужі проєкти: це найкращий, найпростіший і найлегший спосіб перейняти всю мудрість, яка десятиріччями накопичувалася в ІТ-індустрії.

І, до речі, у вас уже є напохваті чудовий, популярний, добре документований проєкт — Java SDK. Почніть із нього.

Розбирайте класи й структури класів. Думайте, чому одні методи зроблено статичними, а інші — ні. Чому методи мають саме такі параметри, а не інші. Чому саме такі методи, чому класи називаються саме так і містяться саме в таких пакетах.

Коли ви почнете розуміти відповіді на всі ці запитання, ви зможете писати код, зрозумілий іншим.

Проте хочу застерегти вас від розбору коду в методах Java SDK. Код багатьох методів було переписано з метою максимального збільшення швидкості роботи, тому такий код не завжди легкочитний.

Правильно розбивати програму на логічні частини

Будь-яку програму зазвичай розбивають на частини або модулі. Кожна частина відповідає за свій аспект програми.

От у комп'ютера є системний блок, монітор, клавіатура, і все це — окремі, малозалежні один від одного компоненти. Ба більше, їх взаємодію стандартизовано: USB, HDMI тощо. Натомість якщо ви проллєте каву на клавіатуру, її можна просто помити під краном, просушити й користуватися далі.

А от ноутбук — це приклад монолітної архітектури: логічні частини нібито і є, але інтегровані вони між собою набагато щільніше. Приміром, щоб почистити клавіатуру MacBookPro, потрібно розібрати половину ноутбука. А якщо пролити на нього чай, доведеться замовити новий. Але це буде не чай.


3. Створення власних класів

Оскільки ви лише вчитеся програмувати, слід починати з малого — вчитися створювати власні класи.

Ви їх, звісно, вже створювали, але вам важливо розвивати розуміння того, які класи мають бути в програмі, як вони мають називатися, які в них мають бути методи. І як вони мають один з одним взаємодіяти.

Список сутностей

Якщо ви не знаєте, з чого почати, почніть з початку.

На самому початку проєктування програми ви можете просто записати на аркуші список сутностей (об'єктів), які вона має містити. А відтак запрограмувати їх за таким принципом: кожна сутність — окремий клас.

Приклад

Припустімо, ви хочете написати гру в шахи. Вам знадобляться такі сутності: шахівниця і 6 типів фігур. Фігури ходять по-різному, мають різну цінність — логічно, що це окремі класи. І взагалі, на самому початку що більше класів, то краще.

Зустріти програміста-початківця, який замість двох класів написав би десять, — велика рідкість. От замість десяти створити два, а то й один — це новачки полюбляють. Отже, більше класів, панове програмісти. І ваш код стане зрозумілішим для всіх, окрім, можливо, вас 😛

Шахи

Припустімо, ми вирішили створити класи для шахів: який вигляд вони матимуть?

Шахівниця — це просто масив 8 на 8? Краще зробіть для неї окремий клас, який усередині зберігає посилання на масив. Тоді в клас «Шахівниця» ви зможете додати багато корисних методів, які, наприклад, перевіряють, клітинка порожня чи зайнята.

Загалом кажучи, на початку завжди можна керуватися таким принципом: У програмі є різні Сутності, а Сутність має тип. Оцей тип як раз і є класом.


4. Статичні змінні й методи

Також не забувайте користуватися статичними змінними й методами. Якщо у вас одна шахова фігура взаємодіє з іншою на шахівниці, значить, ваш код має містити метод, в який передаються посилання на першу фігуру, на другу та на шахівницю.

Щоби постійно не передавати посилання на об'єкти, які «існують завжди», їх зазвичай роблять статичними змінними, і до них можна звернутися з будь-якого місця програми.

Наприклад, отак:

Код Примітка
public class ChessBoard
{
   public static ChessBoard board = new ChessBoard();
   public ChessItem[][] cells = new ChessItem[8][8];
   ...
}

public class Game
{
   public static void main(String[] args)
   {
      var board = ChessBoard.board;
      board.cells[0][3] = new King(Color.WHITE);
      board.cells[0][4] = new Queen(Color.WHITE);
      ...
   }
}


Посилання на єдиний об'єкт типу ChessBoard.
Двовимірний масив 8×8, нестатична змінна.








Додаємо фігури на шахівницю.

Також замість статичної змінної можна зробити метод, який повертає єдиний об'єкт. Наприклад, отак:

public class ChessBoard
{
   private static ChessBoard board = new ChessBoard();
   public static ChessBoard getBoard()
   {
      return board;
   }

   public ChessItem[][] cells = new ChessItem[8][8];
   ...
}

public class Game
{
   public static void main(String[] args)
   {
      var board = ChessBoard.getBoard();
      board.cells[0][3] = new King(Color.WHITE);
      board.cells[0][4] = new Queen(Color.WHITE);
      ...
   }
}