Примітиви, такі як string
або int
, не мають ніякого значення в бізнесі. Прямим наслідком цього є те, що ви можете помилково використовувати URL, коли очікується ідентифікатор товару, або використовувати кількість, коли очікуєте ціну .
Ось чому виклик предметів Calisthenics є одним із його правил: обгортання примітивів:
Правило 3: Обмотайте всі примітиви та струни
У мові Java int є примітивним, а не реальним об'єктом, тому він підкоряється іншим правилам, ніж об'єкти. Він використовується з синтаксисом, не орієнтованим на об'єкти. Що ще важливіше, сам по собі інт - це просто скаляр, тому він не має ніякого значення. Коли метод приймає int як параметр, ім'я методу повинно виконувати всю роботу з вираження наміру. Якщо той же метод приймає за параметр Година, набагато простіше зрозуміти, що відбувається.
Цей же документ пояснює, що є додаткова вигода:
Невеликі предмети, такі як Година або Гроші, також дають нам очевидне місце для розміщення поведінки, яка б інакше була засмічена навколо інших класів .
Дійсно, коли використовуються примітиви, зазвичай надзвичайно важко відстежити точне розташування коду, пов’язаного з цими типами, що часто призводить до суворого дублювання коду . Якщо є Price: Money
клас, то природно знайти перевірку діапазону всередині. Якщо замість цього int
(гірше, а double
) використовується для зберігання цін на продукцію, хто повинен перевірити асортимент? Продукт? Знижка? Візок?
Нарешті, третя перевага, яка не згадується в документі, - це можливість порівняно легко змінити базовий тип. Якщо сьогодні мій тип ProductId
має short
його основний тип, а пізніше мені потрібно використовувати його int
, швидше за все, код для зміни не буде охоплювати всю базу коду.
Недолік - і той самий аргумент стосується кожного правила вправи «Об'єктна гігієніка» - полягає в тому, що якщо швидко стає занадто непосильним, щоб створити клас для всього . Якщо Product
міститься, ProductPrice
що успадковує, від PositivePrice
чого успадковує, від Price
чого в свою чергу успадковує Money
, це не чиста архітектура, а скоріше повний безлад, де для того, щоб знайти одну річ, сервіс повинен кожного разу відкривати кілька десятків файлів.
Ще один момент, який слід врахувати, - це вартість (з точки зору рядків коду) створення додаткових класів. Якщо обгортки незмінні (як це має бути, як правило), це означає, що якщо ми візьмемо C #, ви повинні мати в обгортці щонайменше:
- Власник,
- Поле підкладки,
- Конструктор, який присвоює значення поле резервного копіювання,
- Звичай
ToString()
,
- Зауваження щодо документації XML (що складає багато рядків),
- A
Equals
і GetHashCode
переоцінка (також багато LOC).
і в кінцевому підсумку, якщо це доречно:
- DebuggerDisplay атрибут,
- Перебор
==
і !=
операторів,
- Врешті-решт, перевантаження оператора неявного перетворення для безперебійного перетворення в інкапсульований тип та з нього,
- Код контрактів (включаючи інваріант, який є досить тривалим методом, з трьома його ознаками),
- Кілька перетворювачів, які будуть використовуватися під час серіалізації XML, серіалізації JSON або зберігання / завантаження значення в / з бази даних.
Сотня LOC для простої обгортки робить її досить заборонною, тому ви можете бути повністю впевнені в довгостроковій прибутковості такої обгортки. Поняття обсягу, пояснене Томасом Юнком , особливо актуальне тут. Написання сотень локальних місць для представлення ProductId
використаних у всій базі коду виглядає досить корисним. Написання класу такого розміру для фрагмента коду, який складає три рядки в рамках одного методу, набагато сумнівніше.
Висновок:
Загортайте примітиви в класи, які мають значення у бізнес-області програми, коли (1) це сприяє зменшенню помилок, (2) зменшує ризик дублювання коду або (3) допомагає змінити базовий тип пізніше.
Не обертайте автоматично кожного примітиву, який ви знайдете у своєму коді: є багато випадків, коли користуються string
або int
ідеально добре.
На практиці public string CreateNewThing()
повернення екземпляра ThingId
класу замість string
може допомогти, але ви також можете:
Поверніть екземпляр Id<string>
класу, який є об'єктом загального типу, що вказує, що базовим типом є рядок. Ви маєте перевагу читабельності, без недоліку необхідності підтримувати багато типів.
Повернути примірник Thing
класу. Якщо користувачеві потрібен лише ідентифікатор, це можна легко зробити за допомогою:
var thing = this.CreateNewThing();
var id = thing.Id;