Легко реализовать шаблон, который может принести большую пользу вашей кодовой базе.

В этой статье, после базового определения, я покажу, как писать различные типы VO, а затем попытаюсь проиллюстрировать некоторые быстрые победы, полученные с помощью этого шаблона, на основе ситуаций, с которыми я часто сталкиваюсь.

Объект значения

Я впервые узнал о шаблоне Value Object (VO) в книге Domain Driven Design среди других строительных блоков для уровня предметной области.

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

Краткое определение

Это объект, равенство которого основано на всех свойствах.
Это означает, что он имеет только одно состояние и не может быть изменен. Следовательно, он неизменен.

Выполнение

На большинстве языков это легко реализовать.

Класс

Простой пример: VO, представляющий положительное число с классом данных.

У класса должны быть равные / хэш-методы, геттеры (но не сеттеры)

Проверка (не null, формат и т. Д.) Параметров выполняется в конструкторе, и если это не так, он завершается ошибкой, вызывая исключение.

Enum

Другой пример: использование VO для представления валют с помощью перечисления.

Поведение

К ВО можно добавить дополнительную логику. Например, amount может иметь операции.

Вы можете заметить, что создается новый экземпляр с новым значением.

Сочинение

Наконец, мы можем скомпоновать их в другой ВО.

Вы можете заметить, что методы содержат проверку параметров, как в конструкторе. Затем он делегирует операции Amount.

Я добавляю несколько фабричных методов для лаконичного создания экземпляра. Может пригодиться в тестовом коде.

Преимущества

1. Сильный набор текста

Это не то же самое, что иметь

rateCalculation(age: int)

чем иметь

rateCalculation(age: Age)

В первом случае метод должен будет проверить, действителен ли параметр (положительный)

Во втором случае эта логика содержится в ВО `Age`, и метод rateCalculation может сосредоточиться на его ответственности.

Более того, факт централизации ограничений на значения позволяет избежать где-то возможных ошибок по невнимательности.

2. Явные концепции

Это не то же самое, что иметь

class Product  {
  // …
  double price
  String currency
}

чем иметь

class Product  {
  // …
  Price price
}
class Price  {
  double amount
  Currency currency
}
enum Currency  {
  USD, EUR, CHF, …
}

В первом случае ничто не указывает на то, что два свойства среди других сильно связаны.

Во втором случае VO Price явно указывает, что он состоит из суммы и валюты.

3. Больше никаких классов Utils;)

Это не то же самое, что иметь

isChild(age:int)

чем иметь

age.isChild()

В первом случае, поскольку мы используем примитивный тип, мы часто будем использовать класс Utils со статическим методом. Не очень ООП

Во втором случае у нас есть метод непосредственно на подержанном автомобиле, который проще в использовании.

4. Больше никаких грязных тестов.

Это не то же самое, что иметь

class PersonTest{
  //… large setup
  //… lots of tests
  @Test
  fun ageFromBirthDate(){
    val person = Person(‘’, ’’, Date(20,APRIL, 1991), …)
    person.age shouldBe 30
  }
  @Test
  fun ageFromBirthDate_currentYear(){
    val person = Person(‘’, ’’, Date(20,APRIL, 2021), …)
    person.age shouldBe 0
  }
  @Test
  fun ageFromBirthDate_dependingOnDay(){
    val person = Person(‘’, ’’, Date(20,DECEMBER, 1991), …)
    person.age shouldBe 29
  }
  //… other tests
}

чем иметь

class AgeTest{
  @Test
  fun fromDate(){
    val age = Age.from(Date(20,APRIL, 1991))
    age shouldBe Age(30)
  }
  @Test
  fun ageFromBirthDate_currentYear(){
    val age = Age.from(Date(20,APRIL, 2021))
    age shouldBe Age(0)
  }
  @Test
  fun ageFromBirthDate_dependingOnDay(){
    val age = Age.from(Date(20,DECEMBER, 1991))
    age shouldBe Age(29)
  }
}

В первом случае есть много отвлекающих факторов, которые могут затруднить внесение изменений:

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

Во втором случае отдельный текстовый класс содержит конкретные варианты использования с. Тесты краткие, содержат только необходимую информацию. А на самом деле тестов в PersonTest меньше.

Эти 3 небольших примера делают ваш код и тесты более читаемыми и удобными. Повторяя эти рефакторинги везде, где вы можете, ваш код станет легче писать, и в нем будет меньше ошибок, несмотря на наличие большего количества классов.

Когда начать?

Другие довольно распространенные варианты использования, помимо денег:

  • Номер телефона
  • Возраст
  • Период
  • Процент
  • Цена
  • Геолокация
  • везде у вас есть ограничения по стоимости

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

  • Классы Utils или Helper
  • Крупные компании с более чем 10 объектами недвижимости