Паттерн “Специфікація“ в JS чи потрібен він?

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

Що це таке?

Специфікація - це патерн проектування який дозволяє комбінувати бізнес-правила за допомогою булевої логіки. Складно? От і мені так здалось на перший погляд. Насправді найпростіша реалізація цього патерну виглядає ось так

class ByIdSpecification {
  constructor(private id: string) {}

  isSatisfiedBy(candidate: any): boolean {
    return candidate.id === this.id
  }

}

Як це використовувати?

const array = [{ id: 1, name: "Viktor" }, {id: 2, name: "Oleg"}]

const specification = new ByIdSpecification(1)

const result = array.find(specification.isSatisfiedBy)

Тепер виглядає простіше. Але для чого це? Виглядає як оверінженірінг. Бо цю саму задачу можно зробити за допомогою набагато меншого коду.

Що ж подивимось туди де це використовується найчастіше С#. Тут цей паттерн використовують для того щоб уніфікувати запити до бази данних. Так як фреймворк Entity спілкується з базою за допомогою функцій подібно функції filter або find в JS

Але в JS а конкретно в Node.js коли мі спілкуємось с базою за допомогою query обєктів. Наприклад в одній з найпопулярніших баз данний MongoDB звичайний запит до моделі виглядає от так

model.find({ something: 2 })

Я пропоную свою реалізацію

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

class ByIdSpecification {
  constructor(private id: string) {}

  isSatisfiedBy() {
    return {
      id: this.id
    }
  }

}

model.find(new ByIdSpecification(1).isSatisfiedBy())

Розумію що все ще виглядає як оверінженірінг. Але навіть зараз ми вже маємо 2 переваги.

  1. Збільшена читаємість

  2. Можливість перевикористання

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

class GeoSpecification {
  constructor(private lat: number, private lng: number){}

  isSatisfiedBy() {
    return {
      lat: this.lat,
      lng: this.lng
    }
  }

}

Але головна фішка ціього паттерну попереду.

Булева логіка в кущах

Доки я повністю не зрозумів цю частину, я не бу наскільки закоханим у цей паттерн. Специфікації можно комбінувати. В визначенні патерна фігурує фраза про булеву логіку. Це найголовніша перевага цього патерну. Подивимось на реалізацію цього в JS. На прикладі одніє булевої функції AND

interface ISpecification {
  isSatifiedBy()
  and(specification: ISpecification): ISpecification
}

class CompositeSpecification implements ISpecification {
  
  isSatisfiedBy() {
    throw NotImplementError()
  }

  and(specification: ISpecification): ISpecification {
    return new AndSpecification(this, specification)
  }

}


class AndSpecification extends CompositeSpecification {
  constructor(left: ISpecification, right specification) {
    super()
    this.left = left;
    this.right = right;
  }

  isSatisfiedBy(): {
    return this.left.isSatisfiedBy() && this.right.isSatifiedBy()
  
  }

}


class ByIdSpecification extends CompositeSpecification {
  constructor(private id: string) {}
  
  isSatisfiedBy() {
    return {
      id: this.id
  } 
}

class GeoSpecification extends CompositeSpecification {
  constructor(private lat: number, private lng: number) {}
  
  isSatisfiedBy() {
    return {
      lat: this.lat,
      lng: this.lng
    }
  }

}

const query = new ByIdSpecification(1)
  .and(new GeoSpecification(123, 321))
  .isSatisfiedBy()

model.find(query)

Тепер ми можемо використовувати комбінації цих специфікації що дозволить нам покращити перевикористовування. Та зменшити повторення. Також можно реалізовувати і інші булеві функції наприклад or але є одне обмеження. Туди пролазять деталі реалізації бази. Наприклад

class OrSpecification extends CompositeSpecification {
  constructor(left: ISpecification, right: ISpecificaition) {
    super()
    this.left = left;
    this.reight = right
  }

  isSatisfiedBy() {
    return {
      $or: [this.left.isSatifiedBy(), this.right.isSatifiedBy()]
    }
  }

}

Але є і хороша частина в цьому. Якщо вам захочеться змінити базу посеред проекту(це буває рідко, а ле хто вас бешкетників знає). То ви зможете змінити спосіб комбінації лише в одному місці

Висновки

Стандартну реалізацію цього патерно можно використовувати наприклад для фронтенда з редаксом. Або будь де де в нас є масив inmemory який ми можемо перебирати за допомогою функцій filter або find. Мою реалізацію можно використовувати на повну силу для того щоб робити пошук по базі. Як це наприклад робиться в С#. Буду радий почути коментарі або критику такого підходу.

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Владислав Лєсной
Владислав Лєсной@unnsin

563Прочитань
4Автори
17Читачі
На Друкарні з 15 квітня

Більше від автора

  • Clear Architecture для бекенда на JS

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

    Теми цього довгочиту:

    Clean Architecture
  • Паттерн “Репозиторій“ в JS

    В попередній статті ми розібрались з паттерном Специфікація Сьогодні ми поговоримо про логічне продовження паттерн репозиторій.

    Теми цього довгочиту:

    Js

Вам також сподобається

  • Смерть вулиці: високий модернізм Ле Корбузьє

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

    Теми цього довгочиту:

    Всесвітня Історія
  • λanguage: Неформальний опис мови

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

    Теми цього довгочиту:

    Js

Коментарі (0)

Підтримайте автора першим.
Напишіть коментар!

Вам також сподобається

  • Смерть вулиці: високий модернізм Ле Корбузьє

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

    Теми цього довгочиту:

    Всесвітня Історія
  • λanguage: Неформальний опис мови

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

    Теми цього довгочиту:

    Js