Skip to main content
Практика завдань

OOP в JavaScript (об'єктно-орієнтоване програмування)

Об'єктно-орієнтоване програмування (OOP) — це парадигма програмування, основана на концепції "об'єктів", які містять дані (пропси) та код (методи). OOP допомагає структурувати код, роблячи його більш зрозумілим, повторно використовуваним та легшим для підтримки.

JavaScript підтримує OOP, але реалізує його дещо інакше, ніж класичні об'єктно-орієнтовані мови (Java, C++). До ES6 (2015) OOP у JavaScript будувалося на прототипах, а з ES6 з'явився синтаксис class, який є синтаксичним цукром над прототипним наслідуванням.

Основні принципи OOP

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

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

Наслідування

Механізм, який дозволяє створювати нові класи на основі існуючих, успадковуючи їх пропси та методи. Це сприяє повторному використанню коду та створенню ієрархій класів.

Поліморфізм

Здатність об'єктів з однаковим інтерфейсом мати різні реалізації. Один і той же метод може поводитися по-різному в залежності від об'єкта, який його викликає.

Абстракція

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

Класи в JavaScript (ES6+)

З ES6 до JavaScript було додано синтаксис класів, що робить код більш читабельним та знайомим для розробників, які ознайомлені з класичним OOP.

Оголошення класу

javascript
class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old`); } celebrateBirthday() { this.age++; console.log(`Happy birthday! Now I'm ${this.age} years old`); } } const person = new Person('Alex', 25); person.greet(); person.celebrateBirthday();

Компоненти класу:

  • constructor — спеціальний метод для ініціалізації об'єкта
  • this — посилання на поточний екземпляр класу
  • Методи класу — функції, доступні всім екземплярам

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

Інкапсуляція дозволяє приховувати внутрішні деталі реалізації та надавати лише необхідний інтерфейс для взаємодії.

Приватні поля (ES2022)

javascript
class BankAccount { #balance = 0; constructor(initialBalance) { this.#balance = initialBalance; } deposit(amount) { if (amount > 0) { this.#balance += amount; console.log(`Deposited ${amount}. Balance: ${this.#balance}`); } } withdraw(amount) { if (amount > 0 && amount <= this.#balance) { this.#balance -= amount; console.log(`Withdrawn ${amount}. Balance: ${this.#balance}`); } else { console.log('Insufficient funds'); } } getBalance() { return this.#balance; } } const account = new BankAccount(1000); account.deposit(500); account.withdraw(200); console.log(account.getBalance());

Приватні поля (з префіксом #) недоступні ззовні класу. Це забезпечує справжню інкапсуляцію.

Геттери та сеттери

javascript
class Rectangle { constructor(width, height) { this._width = width; this._height = height; } get area() { return this._width * this._height; } get perimeter() { return 2 * (this._width + this._height); } set width(value) { if (value > 0) { this._width = value; } else { console.log('Width must be positive'); } } set height(value) { if (value > 0) { this._height = value; } else { console.log('Height must be positive'); } } } const rect = new Rectangle(10, 5); console.log(rect.area); rect.width = 15; console.log(rect.area);

Наслідування

Наслідування дозволяє створювати нові класи на основі існуючих, розширюючи або модифікуючи їх функціональність.

javascript
class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a sound`); } move() { console.log(`${this.name} moves`); } } class Dog extends Animal { constructor(name, breed) { super(name); this.breed = breed; } speak() { console.log(`${this.name} barks: Woof-woof!`); } fetch() { console.log(`${this.name} fetches the ball`); } } class Cat extends Animal { constructor(name, color) { super(name); this.color = color; } speak() { console.log(`${this.name} meows: Meow!`); } scratch() { console.log(`${this.name} scratches`); } } const dog = new Dog('Buddy', 'Labrador'); dog.speak(); dog.move(); dog.fetch(); const cat = new Cat('Whiskers', 'Orange'); cat.speak(); cat.move(); cat.scratch();

Ключові моменти:

  • extends — ключове слово для наслідування
  • super() — викликає конструктор батьківського класу
  • Дочірній клас може переозначити методи батьківського класу (поліморфізм)

Поліморфізм

Поліморфізм дозволяє використовувати об'єкти різних класів через спільний інтерфейс.

javascript
class Shape { constructor(name) { this.name = name; } calculateArea() { throw new Error('Method calculateArea must be implemented'); } describe() { console.log(`This is a ${this.name} with area ${this.calculateArea()}`); } } class Circle extends Shape { constructor(radius) { super('Circle'); this.radius = radius; } calculateArea() { return Math.PI * this.radius ** 2; } } class Square extends Shape { constructor(side) { super('Square'); this.side = side; } calculateArea() { return this.side ** 2; } } class Triangle extends Shape { constructor(base, height) { super('Triangle'); this.base = base; this.height = height; } calculateArea() { return (this.base * this.height) / 2; } } const shapes = [ new Circle(5), new Square(4), new Triangle(6, 3) ]; shapes.forEach(shape => { shape.describe(); });

Кожна фігура реалізує метод calculateArea() по-своєму, але всі можуть використовуватися через спільний інтерфейс.

Статичні методи та пропси

Статичні методи та пропси належать класу, а не його екземплярам.

javascript
class MathHelper { static PI = 3.14159; static add(a, b) { return a + b; } static multiply(a, b) { return a * b; } static circleArea(radius) { return this.PI * radius ** 2; } } console.log(MathHelper.add(5, 3)); console.log(MathHelper.circleArea(10)); console.log(MathHelper.PI);

Фабричні методи

javascript
class User { constructor(name, email, role) { this.name = name; this.email = email; this.role = role; } static createAdmin(name, email) { return new User(name, email, 'admin'); } static createGuest(name) { return new User(name, 'guest@example.com', 'guest'); } static createModerator(name, email) { return new User(name, email, 'moderator'); } getInfo() { return `${this.name} (${this.role}) - ${this.email}`; } } const admin = User.createAdmin('John', 'john@example.com'); const guest = User.createGuest('Guest'); const moderator = User.createModerator('Mary', 'mary@example.com'); console.log(admin.getInfo()); console.log(guest.getInfo()); console.log(moderator.getInfo());

Абстракція

Абстракція дозволяє виділяти основні моменти та приховувати деталі реалізації.

javascript
class Database { connect() { throw new Error('Method connect must be implemented'); } disconnect() { throw new Error('Method disconnect must be implemented'); } query(sql) { throw new Error('Method query must be implemented'); } } class MySQLDatabase extends Database { connect() { console.log('Connecting to MySQL'); } disconnect() { console.log('Disconnecting from MySQL'); } query(sql) { console.log(`Executing MySQL query: ${sql}`); return []; } } class PostgreSQLDatabase extends Database { connect() { console.log('Connecting to PostgreSQL'); } disconnect() { console.log('Disconnecting from PostgreSQL'); } query(sql) { console.log(`Executing PostgreSQL query: ${sql}`); return []; } } function executeQuery(database, sql) { database.connect(); const result = database.query(sql); database.disconnect(); return result; } const mysql = new MySQLDatabase(); const postgres = new PostgreSQLDatabase(); executeQuery(mysql, 'SELECT * FROM users'); executeQuery(postgres, 'SELECT * FROM products');

Композиція проти Наслідування

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

javascript
class Engine { start() { console.log('Engine started'); } stop() { console.log('Engine stopped'); } } class GPS { getLocation() { return { lat: 55.7558, lon: 37.6173 }; } } class Radio { play(station) { console.log(`Playing radio: ${station}`); } } class Car { constructor() { this.engine = new Engine(); this.gps = new GPS(); this.radio = new Radio(); } start() { this.engine.start(); console.log('Car is ready to go'); } navigate() { const location = this.gps.getLocation(); console.log(`Current location: ${location.lat}, ${location.lon}`); } listenMusic(station) { this.radio.play(station); } } const car = new Car(); car.start(); car.navigate(); car.listenMusic('Rock FM');

Переваги композиції:

  • Більш гнучка структура
  • Уникнення глибоких ієрархій наслідування
  • Легше тестувати та модифікувати

Прототипне OOP (до ES6)

До появи класів OOP у JavaScript реалізовувалося через функції-конструктори та прототипи.

javascript
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.greet = function() { console.log(`Hello, I'm ${this.name}`); }; Person.prototype.celebrateBirthday = function() { this.age++; console.log(`Now I'm ${this.age} years old`); }; function Developer(name, age, language) { Person.call(this, name, age); this.language = language; } Developer.prototype = Object.create(Person.prototype); Developer.prototype.constructor = Developer; Developer.prototype.code = function() { console.log(`${this.name} codes in ${this.language}`); }; const dev = new Developer('Anna', 28, 'JavaScript'); dev.greet(); dev.code(); dev.celebrateBirthday();

Важливо:

Сучасний синтаксис класів (ES6+) є синтаксичним цукром над прототипним наслідуванням. Під капотом JavaScript все ще використовує прототипи.

Патерни OOP у JavaScript

Синглтон

javascript
class Singleton { static instance = null; constructor() { if (Singleton.instance) { return Singleton.instance; } Singleton.instance = this; this.data = []; } addData(item) { this.data.push(item); } getData() { return this.data; } } const instance1 = new Singleton(); instance1.addData('First item'); const instance2 = new Singleton(); instance2.addData('Second item'); console.log(instance1 === instance2); console.log(instance1.getData());

Фабрика

javascript
class Button { constructor(text) { this.text = text; } render() { console.log(`Button: ${this.text}`); } } class Input { constructor(placeholder) { this.placeholder = placeholder; } render() { console.log(`Input field: ${this.placeholder}`); } } class Select { constructor(options) { this.options = options; } render() { console.log(`Dropdown: ${this.options.join(', ')}`); } } class FormElementFactory { static createElement(type, config) { switch (type) { case 'button': return new Button(config.text); case 'input': return new Input(config.placeholder); case 'select': return new Select(config.options); default: throw new Error('Unknown element type'); } } } const button = FormElementFactory.createElement('button', { text: 'Submit' }); const input = FormElementFactory.createElement('input', { placeholder: 'Enter name' }); const select = FormElementFactory.createElement('select', { options: ['Option 1', 'Option 2'] }); button.render(); input.render(); select.render();

Переваги OOP

  1. Модульність

Код розділений на незалежні об'єкти, кожен з яких відповідає за свою функціональність. 2. Повторне використання

Класи та об'єкти можуть бути повторно використані в різних частинах програми. 3. Розширюваність

Легко додати нову функціональність через наслідування або композицію. 4. Підтримуваність

Зміни в одному класі не впливають на інші частини системи (при правильній архітектурі). 5. Абстракція

Приховування складності та надання простого інтерфейсу для взаємодії.

Недоліки OOP

  1. Складність

Для малих завдань OOP може бути надмірним. 2. Продуктивність

Створення багатьох об'єктів може вплинути на продуктивність. 3. Глибокі ієрархії

Надмірне використання наслідування призводить до складних і крихких структур. 4. Надмірна абстракція

Занадто багато рівнів абстракції ускладнюють розуміння коду.

Коли використовувати OOP

Підходить для:

  • Великих додатків з багатьма взаємопов'язаними сутностями
  • Проектів, які потребують чіткої структури та ієрархії
  • Систем, які потребують повторного використання коду
  • Командної розробки з чітким розподілом обов'язків

Не підходить для:

  • Простих скриптів та утиліт
  • Функціонального програмування
  • Ситуацій, де композиція функцій є більш ефективною

Підсумок:

OOP у JavaScript — це потужна парадигма для структурування коду. Сучасний синтаксис класів робить код більш читабельним, але важливо пам'ятати, що під капотом JavaScript використовує прототипне наслідування. Вибирайте OOP, коли вам потрібна чітка структура, інкапсуляція та повторне використання коду. Для більш гнучких рішень розгляньте композицію або функціональне програмування.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань