Друкарня від WE.UA

Паттерн “Специфікація“ в 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 we.ua/unnsin

5Довгочити
736Прочитання
17Підписники
На Друкарні з 15 квітня

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

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

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

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

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

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

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

    Js

Це також може зацікавити:

  • Що таке патерни проєктування?

    Патерни проєктування подібні до стандартних конструкцій у будівництві. Наприклад, "патерн Фасад" відповідає фасаду будинку, який приховує деталі і надає простий інтерфейс. "Патерн Одинак" подібний до створення лише одного ключа для доступу до чогось цінного.

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

    Програмування
  • Млини Волині: тіні зниклого промислу

    Вітряк або млин колись був одним із ключових елементів сільського ландшафту Волині. Де стояли вітряки? Який принцип роботи млина? Хто ними володів та куди зникла професія мирошника? Карта вцілілих млинів України

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

    Волинь
  • λanguage: Написання парсера

    Написання парсера - це досить складне завдання. У сутності, він повинен перетворити фрагмент коду у "абстрактне синтаксичне дерево". Це структуроване представлення програми в пам'яті, воно абстрактне в тому сенсі, що не має значення, з яких саме символів складається вихідний код.

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

    Парсер

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

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

Це також може зацікавити:

  • Що таке патерни проєктування?

    Патерни проєктування подібні до стандартних конструкцій у будівництві. Наприклад, "патерн Фасад" відповідає фасаду будинку, який приховує деталі і надає простий інтерфейс. "Патерн Одинак" подібний до створення лише одного ключа для доступу до чогось цінного.

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

    Програмування
  • Млини Волині: тіні зниклого промислу

    Вітряк або млин колись був одним із ключових елементів сільського ландшафту Волині. Де стояли вітряки? Який принцип роботи млина? Хто ними володів та куди зникла професія мирошника? Карта вцілілих млинів України

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

    Волинь
  • λanguage: Написання парсера

    Написання парсера - це досить складне завдання. У сутності, він повинен перетворити фрагмент коду у "абстрактне синтаксичне дерево". Це структуроване представлення програми в пам'яті, воно абстрактне в тому сенсі, що не має значення, з яких саме символів складається вихідний код.

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

    Парсер