Irbisa · Шпаргалка

ООП, SOLID и паттерны проектирования

Классы и ООП в TypeScript, SOLID, DRY, KISS и YAGNI, связность и зацепление, паттерны GoF во фронтенде: observer, module, factory, strategy, adapter, facade, proxy, антипаттерны

Middle-разработчик · 20 вопросов

  1. 01

    Какие четыре принципа ООП существуют и как каждый из них выглядит в TypeScript?

    Инкапсуляция прячет состояние объекта за его методами, абстракция показывает, что объект делает, а не как, наследование позволяет подклассу переиспользовать код класса, а полиморфизм позволяет одному вызову работать с любым объектом подходящего типа.

    // abstraction: callers see what a payment method does, not howinterface PaymentMethod {  pay(amountCents: number): Promise<string>;} // inheritance: keeps message and stack trace, adds a status code…
  2. 02

    Чем private отличается от #private в классе TypeScript и что добавляют protected и readonly?

    private остаётся проверкой TypeScript, которая исчезает после компиляции, поэтому в рантайме поле становится обычным свойством.

    class Account {  private pin = '1234'; // checked by tsc only, a normal property at runtime  #token = 'secret'; // a JavaScript private field, enforced by the engine  protected readonly owner: string;   constructor(owner: string) {…
  3. 03

    Когда в TypeScript использовать абстрактный класс, а когда интерфейс?

    Интерфейс задаёт только контракт: форму без кода, которая исчезает после компиляции.

    // the contract callers depend oninterface Uploader {  upload(file: Blob): Promise<string>;} // shared algorithm; subclasses fill in the steps marked abstract…
  4. 04

    Что означают DRY, KISS и YAGNI и когда устранение дублирования делает фронтенд-код хуже?

    DRY (don't repeat yourself) требует, чтобы каждое знание жило в одном месте, KISS (keep it simple) велит выбирать самое простое работающее решение, а YAGNI (you aren't gonna need it) запрещает строить под воображаемые потребности.

  5. 05

    Что означают слабая связанность (low coupling) и сильная связность (high cohesion) во фронтенд-коде и как распознать код, где нет ни того, ни другого?

    Связанность (coupling) показывает, насколько модуль зависит от деталей других модулей; связность (cohesion) показывает, насколько код внутри одного модуля относится к одному делу.

  6. 06

    Почему в коде на TypeScript композицию обычно предпочитают наследованию и когда наследоваться от класса всё же правильно?

    Наследование привязывает подкласс к внутреннему устройству родителя, поэтому изменение базового класса может сломать подклассы, которых он никогда не видел, а иерархия допускает только одну ось вариаций.

    class TagList {  protected items: string[] = [];  add(tag: string) {    this.items.push(tag);  }  addAll(tags: string[]) {…
  7. 07

    Что говорят пять принципов SOLID и как каждый выглядит в обычном TypeScript: в сервисах, API-клиентах и валидаторах?

    SOLID объединяет пять правил проектирования: единственная ответственность, открытость/закрытость, подстановка Лисков, разделение интерфейсов и инверсия зависимостей.

    // O: each rule is a value; a new rule is a new function, not another `if` in validate()type Rule = (value: string) => string | null; const required: Rule = (v) => (v.trim() ? null : 'Required');const minLength = (n: number): Rule => (v) => (v.length >= n ? null : `At least ${n} characters`);const email: Rule = (v) => (/^\S+@\S+\.\S+$/.test(v) ? null : 'Not an email');…
  8. 08

    GuestCart наследует Cart и бросает ошибку из checkout(). Какой принцип SOLID он нарушает и как исправить дизайн?

    class Cart {  protected items: CartItem[] = [];  add(item: CartItem) { this.items.push(item); }  checkout(api: OrdersApi): Promise<string> { return api.createOrder(this.items); }} // guests can fill a cart but must sign in to payclass GuestCart extends Cart {  override async checkout(): Promise<string> { throw new Error('Sign in to check out'); }} async function buyNow(cart: Cart, item: CartItem, api: OrdersApi) {  cart.add(item);  return cart.checkout(api);}

    Нарушен принцип подстановки Лисков: buyNow написан для любого Cart и ожидает, что checkout() создаст заказ, а GuestCart компилируется и затем бросает ошибку.

  9. 09

    Что такое паттерны Module и Singleton и почему ES-модуль уже является синглтоном?

    Паттерн Module прячет состояние в замыкании и открывает публичный API; Singleton гарантирует один экземпляр с одной глобальной точкой доступа.

    // before ES modules: an IIFE kept its variables private (the Module pattern)var legacyCounter = (function () {  var count = 0;  return { increment: function () { return ++count; } };})();…
  10. 10

    Что такое паттерн Factory и как с помощью фабрики создавать нужный API-клиент для каждого окружения?

    Фабрикой называют функцию или метод, который создаёт объекты и возвращает их как интерфейс, поэтому вызывающий код никогда не называет конкретный класс.

    export interface ApiClient {  get<T>(path: string): Promise<T>;} function httpClient(baseUrl: string): ApiClient {  return {…
  11. 11

    Чем паттерн Adapter отличается от Facade и где во фронтенд-приложении применяется каждый из них?

    Адаптер превращает один интерфейс в тот, которого ждёт ваш код, например DTO бэкенда или SDK в ваши собственные типы.

    // what the backend sendsinterface UserDto {  user_id: number;  full_name: string;  created_at: string; // ISO date}…
  12. 12

    Что такое паттерн Proxy и чем он отличается от объекта Proxy в JavaScript?

    Прокси заменяет собой настоящий объект, имеет тот же интерфейс и управляет доступом к нему: создаёт объект лениво, кэширует результаты, проверяет права или перенаправляет вызовы.

    interface ExchangeRates {  rate(from: string, to: string): Promise<number>;} class HttpExchangeRates implements ExchangeRates {  async rate(from: string, to: string) {…
  13. 13

    Что такое паттерн Decorator и как его реализуют функции-обёртки и декораторы TypeScript вроде @logged?

    Декоратор оборачивает объект или функцию в другой объект с тем же интерфейсом и добавляет поведение вокруг вызова: логирование, замер времени, кэширование, повторы.

    // a wrapper function: the Decorator pattern for a plain functionfunction withTiming<A extends unknown[], R>(name: string, fn: (...args: A) => Promise<R>) {  return async (...args: A): Promise<R> => {    const start = performance.now();    try {      return await fn(...args);…
  14. 14

    Чем паттерн Observer отличается от publish/subscribe и где во фронтенд-коде встречается каждый из них?

    В Observer субъект сам хранит список наблюдателей и вызывает их, поэтому каждый наблюдатель подписывается на конкретный объект.

    import { useSyncExternalStore } from 'react'; // Observer: the subject keeps its own listeners and calls them itselffunction createCounterStore() {  let count = 0;  const listeners = new Set<() => void>();…
  15. 15

    Найдите баг: destroy() вызывает off, но каждая закрытая панель поиска продолжает перерисовываться при каждом изменении стора.

    class SearchPanel {  constructor(readonly store: SearchStore, readonly el: HTMLElement) {    store.on('change', this.render.bind(this));  }  render() {    this.el.textContent = `${this.store.results.length} results`;  }  destroy() {    this.store.off('change', this.render.bind(this));    this.el.remove();  }}

    bind создаёт новую функцию при каждом вызове, поэтому off получает функцию, которую никогда не регистрировали, и ничего не удаляет.

  16. 16

    Что такое паттерн Mediator и чем он отличается от Observer?

    Медиатор координирует группу объектов, которые иначе вызывали бы друг друга: каждый сообщает медиатору о своих изменениях, а медиатор решает, кому ещё нужно измениться.

    type FieldName = 'country' | 'delivery' | 'isCompany' | 'vatId'; interface FieldState {  value: string;  disabled: boolean;  options?: string[];…
  17. 17

    Что такое паттерны Strategy и Command и почему в TypeScript оба часто сводятся к функциям или обычным объектам?

    Strategy делает алгоритм заменяемым: вызывающий передаёт, как считать цену, сортировать или валидировать.

    // Strategy: how shipping is priced, picked by keytype ShippingStrategy = (weightKg: number) => number; const shipping = {  standard: (kg) => 500 + kg * 100,  express: (kg) => 1500 + kg * 250,…
  18. 18

    Какие классические паттерны проектирования JavaScript делает тривиальными благодаря функциям первого класса, замыканиям и модулям, а какие всё ещё требуют проектирования?

    Многие паттерны сводятся к возможности языка: Strategy и Command становятся функциями, Singleton и Module превращаются в ES-модуль, Iterator в протокол итерации.

  19. 19

    Как применить инверсию зависимостей к слою API фронтенда (порты и адаптеры) и когда это не стоит усилий?

    Пусть бизнес-код владеет нужным ему интерфейсом (портом вроде OrdersGateway в своих типах), а код для HTTP, GraphQL или памяти пишется адаптерами, которые его реализуют.

    // orders/model.ts: the port, in the domain's own termsexport interface Order {  id: string;  totalCents: number;  placedAt: Date;}…
  20. 20

    Какие антипаттерны чаще всего встречаются во фронтенд-кодовых базах (god store, свалка utils, неверная абстракция) и как выбраться из них рефакторингом?

    Обычная тройка: god object, который читают и пишут все фичи, папка utils без единого назначения и общий компонент, подогнанный флагами под случаи, для которых его не делали.