Перейти до основного вмісту

ООП у TypeScript

Короткий вступ

TypeScript додає статичну типізацію поверх JavaScript і підтримує класичні концепти ООП: класи, успадкування, інтерфейси, інкапсуляцію, поліморфізм та абстракції. Нижче — практичні приклади та пояснення.

Класи — декларація, конструктор, властивості та методи

class Person {
  public name: string;
  private age: number;
  protected email?: string; // необов’язкове
  static species = 'Homo sapiens';

  constructor(name: string, age: number, email?: string) {
    this.name = name;
    this.age = age;
    this.email = email;
  }

  public greet(): string {
    return `Hi, I'm ${this.name}.`;
  }

  private getBirthYear(currentYear: number): number {
    return currentYear - this.age;
  }
}

const p = new Person('Ivan', 30, 'ivan@example.com');
console.log(p.greet());
// console.log(p.age); // помилка: 'age' приватне
console.log(Person.species);

Успадкування і перевизначення (extends, super)

class Employee extends Person {
  constructor(name: string, age: number, public position: string) {
    super(name, age);
  }

  // Перевизначення методу
  public greet(): string {
    return `${super.greet()} I work as ${this.position}.`;
  }
}

const e = new Employee('Olena', 28, 'Backend Developer');
console.log(e.greet());

Access modifiers: public, private, protected, readonly

public — доступно скрізь; private — лише в межах класу; protected — в межах класу і підкласів; readonly — доступне лише для читання після ініціалізації.

class Config {
  constructor(public readonly env: string, private secret: string) {}
}

const cfg = new Config('production', 's3cr3t');
// cfg.env = 'dev'; // помилка: readonly

Інтерфейси (interface) — контракти для об'єктів і класів

Інтерфейси описують форму об'єкта або класу. Клас може реалізовувати інтерфейс через implements.

interface Serializable {
  serialize(): string;
}

class User implements Serializable {
  constructor(public id: number, public name: string) {}

  serialize(): string {
    return JSON.stringify({ id: this.id, name: this.name });
  }
}

Типи (type aliases) — гнучкіші, ніж інтерфейси

type дозволяє створювати псевдоніми типів, об'єднання, перетини тощо.

type ID = string | number;

type ApiResponse = {
  data: T;
  error?: string;
};

const r: ApiResponse<User> = { data: new User(1, 'Taras') };

Інтерфейс vs Type — коли що використовувати

  • Використовуйте interface для опису публічного API об'єктів та класів (легко розширювати).
  • Використовуйте type для складних аліасів, уніонів, перетинів або для типів, які не описують об'єкти.

Абстрактні класи (abstract) — часткова реалізація

Абстрактний клас може мати частково реалізовані методи та абстрактні методи, які потрібно реалізувати у підкласах.

abstract class Repository {
  abstract getById(id: ID): T | null;

  save(entity: T): void {
    // загальна логіка збереження
    console.log('saving...', entity);
  }
}

class UserRepository extends Repository<User> {
  getById(id: ID): User | null {
    // логіка пошуку
    return new User(Number(id), 'Demo');
  }
}

Поліморфізм — однаковий інтерфейс, різні реалізації

function printSerializable(obj: Serializable) {
  console.log(obj.serialize());
}

const user = new User(2, 'Nastia');
printSerializable(user);
// Можемо передати будь-який об'єкт, що реалізує Serializable

Generics — узагальнені типи

Generics (узагальнення) дозволяють писати код, який працює з різними типами.

class Box<T> {
  constructor(public value: T) {}
  get(): T { return this.value; }
}

const numBox = new Box<number>(123);
const strBox = new Box<string>('hello');

Enum

enum у TypeScript — це спосіб оголосити набір іменованих констант. Вони допомагають зробити код більш читабельним та захищеним від помилок, замінюючи "магічні рядки" або "числові коди" зрозумілими іменами.

Числові enum (Numeric enums)
enum Direction {
  Up,       // 0
  Down,     // 1
  Left,     // 2
  Right     // 3
}

const move: Direction = Direction.Left;
console.log(move);        // 2
console.log(Direction[2]); // "Left"
Рядкові enum (String enums)
enum Status {
  Pending = "PENDING",
  InProgress = "IN_PROGRESS",
  Done = "DONE"
}

function printStatus(s: Status) {
  console.log(`Status: ${s}`);
}

printStatus(Status.InProgress); // Status: IN_PROGRESS
Гетерогенні enum (змішані значення)
enum Mixed {
  No = 0,
  Yes = "YES"
}

console.log(Mixed.No);   // 0
console.log(Mixed.Yes);  // "YES"
Const enum (оптимізація компіляції)

const enum замінюється на значення під час компіляції, не створюючи додатковий об’єкт у JS. Це зменшує накладні витрати, але не дозволяє робити reverse mapping.

const enum LogLevel {
  Info,
  Warn,
  Error
}

const level = LogLevel.Warn;
console.log(level); // 1
Порівняння з об’єктами

У нових проєктах замість enum часто рекомендують використовувати as const з об’єктами для кращої сумісності з JavaScript.

const Roles = {
  Admin: "ADMIN",
  User: "USER",
  Guest: "GUEST"
} as const;

type Role = typeof Roles[keyof typeof Roles];

function assignRole(r: Role) {
  console.log(`Assigned role: ${r}`);
}

assignRole(Roles.Admin);

enum у TypeScript корисний для опису обмежених наборів значень (стани, ролі, напрямки тощо). Проте в сучасних проєктах часто обирають об’єкти з as const, бо вони легші й простіші у взаємодії з JavaScript.

Інкапсуляція та модулі

За замовчуванням кожний файл — окремий модуль. Експортуйте лише те, що потрібно публічно, а внутрішнє тримайте приватним (не експортуйте).

// user-service.ts
class UserServiceInternal {
  // внутрішня логіка
  private helper() { /* ... */ }
}

export class UserService {
  // публічний API, який використовує внутрішню логіку
  private intern = new UserServiceInternal();
  find() { /* ... */ }
}

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

// repo.ts
export interface IUserRepo {
  findById(id: ID): Promise<User | null>;
}

export class UserRepo implements IUserRepo {
  async findById(id: ID) { return new User(Number(id), 'From DB'); }
}

// service.ts
import type { IUserRepo } from './repo';

export class UserService {
  constructor(private repo: IUserRepo) {}

  async getUser(id: ID) {
    return this.repo.findById(id);
  }
}

// app.ts
import { UserRepo } from './repo';
import { UserService } from './service';

const repo = new UserRepo();
const service = new UserService(repo);
service.getUser(1).then(u => console.log(u));

Поради для Java-розробників

  • TypeScript-класи дуже схожі на Java-класи — конструкції знайомі, але синтаксис дещо інший.
  • Використовуйте інтерфейси для контрактів і type для складних типових виразів.
  • При побудові архітектури застосовуйте ті самі принципи SOLID, що й у Java.
  • Пам'ятайте, що на рантаймі TypeScript перетворюється в JavaScript — модифікатори доступу (private) забезпечуються транслятором/TypeScript-інструментами, але не є такими ж "залізними", як у JVM.

TypeScript дає всі основні інструменти ООП, знайомі Java-розробникам, при цьому додає гнучкість JavaScript-екосистеми. Правильне використання класів, інтерфейсів, generics та модулів допоможе будувати підтримувану та різко типізовану архітектуру на Node.js.

Коментарі

Популярні публікації

Колекції в Elixir

Списки Що таке список у Elixir У Elixir список — це впорядкована колекція елементів, реалізована як однозв’язний список . Це означає, що кожен елемент (вузол) зберігає посилання на наступний, але не на попередній. Така структура дозволяє швидко додавати елементи на початок списку, але повільно доступати до довільного елементу. У Java подібну структуру представляє LinkedList — частина Java Collections Framework. Вона реалізована як двозв’язний список, що забезпечує зручне додавання/видалення елементів з початку або кінця списку. Створення списків У Elixir список створюється за допомогою квадратних дужок: list = [1, 2, 3, 4] Додавання елементів У Elixir новий елемент можна додати тільки на початок списку за допомогою оператора | : # Elixir list = [2, 3, 4] new_list = [1 | list] # [1, 2, 3, 4] Доступ до елементів У Elixir немає прямого доступу до елементів за індексом, але це можна зробити через Enum.at : Enum.at([10, 20, 30], 1) # 20 Ітерація по списку У Elixir ...

Інструменти для роботи з Node.js

Що таке npm? npm (Node Package Manager) — це офіційний пакетний менеджер для Node.js . Він дозволяє: Встановлювати сторонні бібліотеки та фреймворки Керувати залежностями проєкту Запускати скрипти (команди) через package.json Приклад ініціалізації проєкту з npm npm init -y Файл package.json (скорочений приклад) { "name": "my-project", "version": "1.0.0", "scripts": { "start": "ts-node src/index.ts", "build": "tsc" }, "dependencies": { "express": "^4.18.0" }, "devDependencies": { "typescript": "^5.0.0", "ts-node": "^10.0.0" } } Що таке tsconfig.json? tsconfig.json — це файл конфігурації для компілятора TypeScript, який визначає, як слід компілювати код. Приклад файлу tsconfig.json { "compilerOptions": { "target": "ES2020...

Атоми в мові програмування Elixir

Атоми в Elixir Атоми є фундаментальною концепцією в Elixir , що відіграє ключову роль у створенні надійних та масштабованих систем. В Elixir це специфічний тип даних, який є константою , незмінною , ідентифікованою за своїм ім'ям . Отже, атом в Elixir — це іменована константа, що представляє себе. Уявіть, що ви даєте унікальне ім'я певній речі, і це ім'я завжди посилається саме на цю річ, і ніколи на щось інше. Наприклад, атом :ok завжди буде означати саме успішне завершення операції, а не якесь інше значення. Технічно, атоми є похідними від чисел . Кожен унікальний атом зберігається у таблиці атомів, і йому присвоюється унікальний цілочисельний ідентифікатор. Це робить їх надзвичайно ефективними для порівняння: замість порівняння рядків (що є повільною операцією), Elixir порівнює цілочисельні ідентифікатори. Переваги та особливості використання атомів Переваги атомів: Ефективність. Завдяки своєму числовому представленню, порівняння атомів є дуже швидким. Це осо...

Встановлення PostgreSQL на Ubuntu-сервер

Встановлення Оновлюємо пакети та встановлюємо PostgreSQL: sudo apt update sudo apt install -y postgresql postgresql-contrib Перевіряємо статус сервісу: sudo systemctl status postgresql Якщо PostgreSQL не запущений, запустимо його: sudo systemctl start postgresql sudo systemctl enable postgresql Налаштування безпеки Зміна пароля: sudo -u postgres psql У консолі PostgreSQL: ALTER USER postgres PASSWORD 'міцний_пароль'; \q \q - вихід з консолі. Список основних команд для роботи з PostgreSQL можна переглянути за посиланням. За замовчуванням PostgreSQL слухає localhost (127.0.0.1). Щоб дозволити доступ із зовнішніх машин, редагуємо конфігурацію: sudo nano /etc/postgresql/17/main/postgresql.conf (замість 17 вкажи версію PostgreSQL, яку встановлено) Шукаємо рядок: #listen_addresses = 'localhost' та замінюємо на listen_addresses = '*' Зберігаємо (Ctrl + X, Y, Enter). Тепер редагуємо pg_hba.conf: sudo nano /etc/postgresql/17/main/pg_hba.conf...

Основи Node.js

Що таке Node.js? Node.js — це середовище виконання JavaScript поза браузером, побудоване на рушії Google V8 . Воно дозволяє запускати JavaScript на сервері, створюючи серверні застосунки з високою продуктивністю. Основні характеристики: Однопотокова модель з неблокуючим I/O Асинхронне виконання за допомогою event loop Висока продуктивність у роботі з мережевими запитами npm — найбільший реєстр пакетів Що таке event loop? Event loop — це механізм в Node.js, який дозволяє неблокуючим асинхронним операціям виконуватись у середовищі з єдиним потоком. Він постійно перевіряє наявність подій у черзі та викликає відповідні колбеки. Як працює однопоточність у Node.js? Node.js використовує один потік (main thread) для обробки JavaScript-коду. Операції, які займають час (мережеві запити, читання з файлової системи), делегуються до системних API або thread pool, і після завершення результат повертається у основний потік через event loop. Приклад: асинхронна о...