JavaRush /Java блог /Random UA /Кава-брейк #90. 4 стовпи об'єктно-орієнтованого програмув...

Кава-брейк #90. 4 стовпи об'єктно-орієнтованого програмування

Стаття з групи Random UA
Джерело: The Geek Asian Погляньмо на чотири основи об'єктно-орієнтованого програмування і спробуємо зрозуміти принципи їх роботи. Об'єктно-орієнтоване програмування (ООП) – одна з основних парадигм програмування. Воно може бути легким і простим або навпаки дуже складним. Все залежить від того, як ви вирішабо розробити свою програму. Кава-брейк #90.  4 стовпи об'єктно-орієнтованого програмування - 1Існує 4 стовпи ООП:
  1. Інкапсуляція.
  2. Спадкування.
  3. Анотація.
  4. Поліморфізм.
Тепер ми обговоримо кожен із них з коротким поясненням та реальним прикладом коду.

1. Інкапсуляція

Всі ми вивчали інкапсуляцію як приховування елементів даних та надання користувачам доступу до даних за допомогою загальнодоступних методів. Ми це називаємо гетерами та сеттерами. А зараз забудемо про це і знайдемо простішу визначення. Інкапсуляція - це метод обмеження користувача від прямої зміни елементів даних або змінних класу для підтримки цілісності даних. Як ми це робимо? Ми обмежуємо доступ до змінних, переключаючи модифікатор доступу на private та відкриваючи публічні методи, які можна використовувати для доступу до даних. Погляньмо на конкретні приклади нижче. Це допоможе нам зрозуміти, як ми можемо використовувати інкапсуляцію для підтримки цілісності даних. Без інкапсуляції:
/**
 * @author thegeekyasian.com
 */
public class Account {

  public double balance;

  public static void main(String[] args) {

  	Account theGeekyAsianAccount = new Account();

  	theGeekyAsianAccount.balance = -54;
  }
}
У наведеному вище фрагменті коду метод main() звертається безпосередньо до змінної balance . Це дозволяє користувачеві встановити будь-яке подвійне значення для змінної balance класу Account . Ми можемо втратити цілісність даних, дозволивши кожному встановити значення balance як будь-яке неприпустиме число, наприклад -54 в даному випадку. З інкапсуляцією:
/**
 * @author thegeekyasian.com
 */
public class Account {

  private double balance;

  public void setBalance(double balance) {

    if(balance >= 0) { // Validating input data in order to maintain data integrity
	  this.balance = balance;
    }

    throw new IllegalArgumentException("Balance cannot be less than zero (0)");
  }

  public static void main(String[] args) {

  	Account theGeekyAsianAccount = new Account();

  	theGeekyAsianAccount.setBalance(1); // Valid input - Allowed
  	theGeekyAsianAccount.setBalance(-55); // Stops user and throws exception
  }
}
У цьому коді ми обмежабо доступ до змінної balance і додали метод setBalance() , який дозволяє користувачам встановлювати значення balance для Account . Інсталятор перевіряє надане значення перед присвоєнням його змінної. Якщо значення менше нуля, генерується виняток. Це гарантує, що цілісність даних не буде порушено. Після пояснення наведених вище прикладів я сподіваюся, що цінність інкапсуляції як одного з чотирьох стовпів ОВП є очевидною.

2. Спадкування

Спадкування - це метод отримання властивостей іншого класу, що має спільні риси. Це дозволяє нам збільшити можливість повторного використання та зменшити дублювання коду. Метод також має принцип взаємодії child-parent, коли дочірній елемент успадковує властивості свого батька. Давайте заглибимося в два короткі приклади і подивимося, як успадкування робить код більш простим і багаторазовим. Без успадкування:
/**
 * @author thegeekyasian
 */
public class Rectangle {

  private int width;
  private int height;

  public Rectangle(int width, int height) {
	this.width = width;
	this.height = height;
  }

  public int getArea() {
	return width * height;
  }
}

public class Square {

  private int width; // Duplicate property, also used in class Rectangle

  public Square(int width) {
	this.width = width;
  }

  public int getArea() { // Duplicate method, similar to the class Rectangle
	return this.width * this.width;
  }
}
Два подібних класу мають загальні властивості width і метод getArea() . Ми можемо збільшити можливість повторного використання коду, виконавши невеликий рефакторинг, коли клас Square зрештою успадковує клас Rectangle . З успадкуванням:
/**
 * @author thegeekyasian
 */
public class Rectangle {

  private int width;
  private int height;

  public Rectangle(int width, int height) {
	this.width = width;
	this.height = height;
  }

  public int getArea() {
	return width * height;
  }
}

public class Square extends Rectangle {

  public Square(int width) {
	super(width, width); // A rectangle with the same height as width is a square
  }
}
Просто розширивши клас Rectangle ми отримаємо клас Square у вигляді типу Rectangle . Це означає, що він успадкував всі властивості, загальні для Square та Rectangle . У наведених вище прикладах бачимо, як успадкування грає значної ролі у забезпеченні можливості повторного використання коду. Воно також дозволяє класу успадковувати поведінку батьківського класу.

3. Абстракція

Абстракція – це метод надання користувачеві лише суттєвих деталей шляхом приховування непотрібних чи нерелевантних деталей об'єкта. Він допомагає зменшити операційну складність на стороні користувача. Абстракція дозволяє нам надати користувачеві простий інтерфейс, не вимагаючи складних деталей для виконання дії. Простіше кажучи, дає користувачеві можливість керувати автомобілем, не вимагаючи розуміння того, як працює двигун. Спершу розглянемо приклад, а потім обговоримо, як нам допомагає абстракція.
/**
* @author thegeekyasian.com
*/
public class Car {

  public void lock() {}
  public void unlock() {}

  public void startCar() {

	checkFuel();
	checkBattery();
	whatHappensWhenTheCarStarts();
  }

  private void checkFuel() {
	// Check fuel level
  }

  private void checkBattery() {
	// Check car battery
  }

  private void whatHappensWhenTheCarStarts() {
	// Magic happens here
  }
}
У наведеному вище коді методи lock() , unlock() і startCar() є публічними, а інші є приватними по відношенню до класу. Ми спростабо користувачеві "керування автомобілем". Звичайно, він може вручну перевірити checkFuel() та checkBattery() перед запуском автомобіля startCar() , але це лише ускладнить процес. З наведеним вище кодом все, що потрібно зробити користувачеві, це використовувати startCar() , а про інше подбає клас. Це те, що ми називаємо абстракцією.

4. Поліморфізм

Останній і найважливіший із чотирьох стовпів ОВП — це поліморфізм. Поліморфізм означає "безліч форм". Судячи з назви, це функція, яка дозволяє виконувати дію кількома чи різними способами. Коли ми говоримо про поліморфізм, обговорювати особливо нічого, якщо ми не говоримо про його типи. Існує два типи поліморфізму:
  1. Перевантаження методу – статичний поліморфізм (Static Binding).
  2. Перевизначення методу – динамічний поліморфізм (Dynamic Binding).
Давайте обговоримо кожен із цих типів і подивимося, у чому між ними різниця.

Перевантаження методу – статичний поліморфізм:

Перевантаження методу або статичний поліморфізм, також відомий як статичне зв'язування (Static Binding) або зв'язування під час компіляції, - це тип, у якому виклики методів визначаються під час компіляції. Перевантаження методів дозволяє нам мати кілька методів з тим самим ім'ям, що мають різні типи даних параметра, або різну кількість параметрів, або те й інше. Але питання у тому, чим корисне навантаження методу (або статичний поліморфізм)? Давайте подивимося на наведені нижче приклади, щоб краще зрозуміти навантаження методів. Без навантаження методу:
/**
* @author thegeekyasian.com
*/
public class Number {

  public void sumInt(int a, int b) {
	System.out.println("Sum: " + (a + b));
  }

  public void sumDouble(double a, double b) {
	System.out.println("Sum: " + (a + b));
  }

  public static void main(String[] args) {

	Number number = new Number();

	number.sumInt(1, 2);
	number.sumDouble(1.8, 2.5);
  }
}
У наведеному вище прикладі ми створабо два методи з різними іменами, просто щоб додати два різні типи чисел. Якщо ми продовжимо аналогічну реалізацію, ми матимемо кілька методів з різними іменами. Це знизить якість та доступність коду. Щоб покращити це, ми можемо використовувати перевантаження методів, застосовуючи одну й ту саму назву для різних методів. Це дозволить користувачеві мати одну опцію як точку входу для підсумовування різних типів чисел. Перевантаження методу працює, коли два або більше методів мають однакове ім'я, але різні параметри. Тип значення, що повертається може бути як однаковим, так і різним. Але якщо два методи мають однакове ім'я, однакові параметри, але різні типи значень, що повертаються, то це викличе перевантаження і помилку компіляції! З навантаженням методу:
/**
* @author thegeekyasian.com
*/
public class Number {

  public void sum(int a, int b) {
	System.out.println("Sum: " + (a + b));
  }

  public void sum(double a, double b) {
	System.out.println("Sum: " + (a + b));
  }

  public static void main(String[] args) {

	Number number = new Number();

	number.sum(1, 2);
	number.sum(1.8, 2.5);
  }
}
У тому ж коді з невеликими змінами ми змогли перевантажити обидва методи, зробивши імена однаковими для обох. Тепер користувач може вказати свої конкретні типи даних як параметри методу. Потім він виконуватиме дію на основі наданого їм типу даних. Ця прив'язка методів виконується під час компіляції, оскільки компілятор знає, який метод буде викликаний зазначеним типом параметра. Саме тому ми називаємо це прив'язкою під час компіляції.

Перевизначення методу - динамічний поліморфізм:

На відміну від перевантаження методу, перевизначення методу дозволяє вам мати таку саму сигнатуру, що і кілька методів, але вони повинні бути в декількох різних класах. Питання, що в цьому особливого? Ці класи мають відношення IS-A, тобто повинні мати спадкування між собою. Іншими словами, при перевизначенні методу або динамічному поліморфізм методи динамічно обробляються під час виконання при виклику методу. Це робиться на основі посилання на об'єкт, яким він ініціалізується. Ось невеликий приклад перевизначення методу:
/**
* @author thegeekyasian.com
*/
public class Animal {

  public void walk() {
	System.out.println("Animal walks");
  }
}

public class Cat extends Animal {

  @Override
  public void walk() {
	System.out.println("Cat walks");
  }
}

public class Dog extends Animal {

  @Override
  public void walk() {
	System.out.println("Dog walks");
  }
}

public class Main {

  public static void main(String[] args) {

	Animal animal = new Animal();
	animal.walk(); // Animal walks

	Cat cat = new Cat();
	cat.walk(); // Cat walks

	Dog dog = new Dog();
	dog.walk(); // Dog walks

	Animal animalCat = new Cat(); // Dynamic Polymorphism
	animalCat.walk(); // Cat walks

	Animal animalDog = new Dog(); // Dynamic Polymorphism
	animalDog.walk(); //Dog walks
  }
}
У цьому прикладі перевизначення ми динамічно надали об'єкти типу “Dog” та “Cat” типу “Animal”. Це дозволяє нам викликати метод walk() з екземплярами посилань динамічно під час виконання. Ми можемо це зробити за допомогою перевизначення методу (або динамічного поліморфізму). На цьому ми завершабо коротке обговорення чотирьох стовпів ОВП, і я сподіваюся, що воно виявиться для вас корисним.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ