Примитивы, такие как string
или int
, не имеют значения в бизнес-сфере. Прямым следствием этого является то, что вы можете по ошибке использовать URL, когда ожидается идентификатор продукта, или использовать количество, когда ожидаете цену .
Вот почему вызов Object Calisthenics включает в себя обертывание примитивов как одно из его правил:
Правило 3: оберните все примитивы и строки
В языке Java int является примитивным, а не реальным объектом, поэтому он подчиняется другим правилам, чем объекты. Он используется с синтаксисом, который не является объектно-ориентированным. Что еще более важно, int сам по себе является просто скаляром, поэтому он не имеет смысла. Когда метод принимает int в качестве параметра, имя метода должно выполнять всю работу по выражению намерения. Если тот же метод принимает Hour в качестве параметра, гораздо проще увидеть, что происходит.
В том же документе объясняется, что есть дополнительное преимущество:
Небольшие объекты, такие как «Час» или «Деньги», также дают нам очевидное место для поведения, которое в противном случае было бы засорено другими классами .
Действительно, когда используются примитивы, обычно чрезвычайно трудно отследить точное местоположение кода, связанного с этими типами, что часто приводит к серьезному дублированию кода . Если есть Price: Money
класс, естественно найти диапазон проверки внутри. Если вместо этого для хранения цен на товары используется int
(хуже, а double
), кто должен проверять диапазон? Продукт? Скидка? Корзина?
Наконец, третье преимущество, не упомянутое в документе, - это возможность относительно легко изменить базовый тип. Если сегодня my ProductId
имеет в short
качестве базового типа, а позже мне нужно использовать int
вместо этого, скорее всего, код для изменения не будет охватывать всю базу кода.
Недостаток - и тот же аргумент применяется к каждому правилу упражнения Object Calisthenics - состоит в том, что если быстро становится слишком подавляющим, чтобы создать класс для всего . Если Product
содержит то, ProductPrice
что наследует, от PositivePrice
которого наследует, от Price
которого, в свою очередь, наследуется Money
, это не чистая архитектура, а скорее полный беспорядок, где для того, чтобы найти одну вещь, сопровождающий должен каждый раз открывать несколько десятков файлов.
Другим важным моментом является стоимость (с точки зрения строк кода) создания дополнительных классов. Если обертки являются неизменяемыми (как обычно и должно быть), это означает, что, если мы берем C #, вы должны иметь в обёртке как минимум:
- Собственник добытчик,
- Его поддерживающее поле,
- Конструктор, который присваивает значение вспомогательному полю,
- Обычай
ToString()
,
- Комментарии к документации XML (что делает много строк),
- А
Equals
и GetHashCode
переопределения (также много LOC).
и в конце концов, когда это уместно:
- DebuggerDisplay атрибут,
- Переопределение
==
и !=
операторы,
- В конечном итоге перегрузка оператора неявного преобразования для плавного преобразования в и из инкапсулированного типа,
- Контракты кода (включая инвариант, который является довольно длинным методом с тремя атрибутами),
- Несколько конвертеров, которые будут использоваться во время сериализации XML, сериализации JSON или сохранения / загрузки значения в / из базы данных.
Сто LOC для простой обертки делает его довольно запретным, и поэтому вы можете быть полностью уверены в долгосрочной прибыльности такой обертки. Понятие объема, объясненное Томасом Джанком , особенно актуально здесь. Написание сотен LOC для представления ProductId
всей используемой базы кода выглядит весьма полезным. Написание класса такого размера для фрагмента кода, который составляет три строки в одном методе, является гораздо более сомнительным.
Заключение:
Оборачивайте примитивы в классы, которые имеют значение в бизнес-области приложения, когда (1) это помогает уменьшить количество ошибок, (2) снижает риск дублирования кода или (3) помогает изменить базовый тип позже.
Не оборачивайте автоматически все примитивы, которые вы найдете в своем коде: во многих случаях используется string
или int
отлично работает.
На практике public string CreateNewThing()
возвращение экземпляра ThingId
класса вместо вместо string
может помочь, но вы также можете:
Вернуть экземпляр Id<string>
класса, который является объектом универсального типа, указывая, что базовый тип является строкой. Вы получаете удобство чтения, без недостатка необходимости поддерживать много типов.
Вернуть экземпляр Thing
класса. Если пользователю нужен только идентификатор, это легко сделать с помощью:
var thing = this.CreateNewThing();
var id = thing.Id;