Примитивы, такие как 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;