Primitive vs Class представляє простий об’єкт домену?


14

Які загальні вказівки чи правила, коли використовувати об’єкт домен-speciifc порівняно із звичайним рядком чи номером?

Приклади:

  • Віковий клас проти цілого?
  • Клас FirstName vs String?
  • UniqueID - String
  • Клас PhoneNumber vs String vs Long?
  • Клас DomainName проти String?

Я думаю, що більшість практикуючих OOP напевно скажуть конкретні класи для PhoneNumber та DomainName. Більше правил щодо того, що робить їх дійсними, і як порівнювати їх, прості заняття простіше і безпечніше мати справу. Але для перших трьох є більше дебатів.

Я ніколи не стикався з класом "Вік", але можна стверджувати, що це має сенс, враховуючи, що він повинен бути негативним (добре, я знаю, ви можете сперечатися за вікові віки, але це хороший приклад, що це майже рівнозначно примітивному цілому числу).

Рядок є загальним для відображення "Імені", але він не є ідеальним, оскільки порожня рядок - це дійсна рядок, але не є дійсною назвою. Порівняння зазвичай робиться ігноруючи випадок. Впевнені, що існують методи перевірити наявність порожніх, порівняти нечутливі до регістру тощо, але це вимагає від споживача цього.

Чи залежить відповідь від оточення? Мене насамперед стосується програмне забезпечення для підприємств / високоцінного програмного забезпечення, яке буде жити та підтримуватися, можливо, більше десяти років.

Можливо, я переосмислюю це, але мені б дуже хотілося знати, чи є у когось правила щодо вибору класу проти примітиву.


2
Віковий клас не є гарною ідеєю, якщо його можна замінити цілим числом. Якщо в конструкторі потрібна дата народження та є методи getCurrentAge () та getAgeAt (Date date), то клас має значення. Але це не може бути замінено цілим числом.
ківірон

5
"Рядок іноді не є рядком. Моделюйте його відповідно." Є хороший пост Марка Семана про "примітивну одержимість": blog.ploeh.dk/2015/01/19/…
Сергій Шушляпін

4
Насправді віковий клас має певний сенс дивним чином. Загальне західне визначення віку - нуль при народженні, і додайте 1 у наступний день народження. Східноазіатська методологія полягає в тому, що ви 1 при народженні і 1 додаєте в наступний новорічний день. Таким чином, залежно від місцевості, ваш вік може бути розрахований по-різному. EG від en.wikipedia.org/wiki/East_Asian_age_reckoning people may be one or two years older in Asian reckoning than in the western age system
Peter M

@PeterM, це гарна інформація! Дати / Таймс і зараз вік. Вони повинні бути простими поняттями, але це доводить, що в світі є набагато складніше, ніж те, з чим ми звикли.
Мачадо

6
Я бачу багато написаного нижче цього питання, але відповідь насправді не така складна. Ви використовуєте Ageклас, а не ціле число, коли вам потрібна додаткова поведінка, яку надав би такий клас.
Роберт Харві

Відповіді:


20

Які загальні вказівки чи правила, коли використовувати об’єкт домен-speciifc порівняно із звичайним рядком чи номером?

Загальне керівництво полягає в тому, що ви хочете моделювати свій домен мовою, що відповідає специфіці домену.

Поміркуйте: чому ми використовуємо цілі числа? Ми можемо представити всі цілі числа з рядками так само легко. Або з байтами.

Якби ми програмували на агностічній доменній мові, яка включала примітивні типи для цілого та віку, який би ви обрали?

Що насправді зводиться до того, що "примітивний" характер певних типів є випадковістю вибору мови для нашої реалізації.

Цифри, зокрема, зазвичай вимагають додаткового контексту. Вік - це не просто число, але він також має розмірність (час), одиниці (роки?), Правила округлення! Додавання вікових груп має сенс таким чином, що додавання віку до грошей не означає.

Визначення типів дозволяє нам моделювати відмінності між неперевіреною електронною адресою та підтвердженою адресою електронної пошти .

Випадки того, як ці значення представлені в пам'яті, є однією з найменш цікавих частин. Модель домену не хвилює CustomerId: це int, або String, або UUID / GUID, або JSON-вузол. Просто хочеться отримати доступності.

Чи нас справді цікавить, чи цілі числа є великими ендіанами, чи маленькими ендіанами? Чи нас хвилює, чи отримано Listнас - це абстракція над масивом чи графіком? Коли ми виявимо, що арифметика подвійної точності неефективна, і що нам потрібно перейти на подання з плаваючою точкою, чи повинна доменна модель доглядати ?

Парнас у 1972 р. Писав

Замість цього ми пропонуємо почати зі списку складних дизайнерських рішень чи дизайнерських рішень, які можуть змінитися. Кожен модуль розроблений для того, щоб приховати таке рішення від інших.

У певному сенсі типи значень для домену, які ми вводимо, - це модулі, які ізолюють наше рішення про те, яке саме подання даних слід використовувати.

Отже, догори - це модульність - ми отримуємо дизайн, де легше керувати сферою зміни. Мінус вартості - це більше роботи над створенням необхідних типів, які вам потрібні, вибір правильних типів вимагає глибшого розуміння домену. Обсяг роботи, необхідний для створення модуля значення, залежатиме від вашого місцевого діалекту Blub .

Інші терміни рівняння можуть включати очікуваний термін експлуатації рішення (ретельне моделювання посуду для сценаріїв, яка буде запущена, як тільки матимуть ослаблену віддачу від інвестицій), наскільки близько домену є основна компетенція бізнесу.

Один особливий випадок, який ми можемо розглянути, - це повідомлення через кордон . Ми не хочемо опинятися в ситуації, коли для зміни одного розгорнутого підрозділу потрібні узгоджені зміни з іншими розгортаними підрозділами. Таким чином, повідомлення, як правило, більше зосереджено на уявленнях, без урахування інваріантів або поведінки, що залежить від домену. Ми не будемо намагатися повідомляти "це значення має бути суворо позитивним" у форматі повідомлення, а скоріше повідомляємо його подання на дроті та застосовуємо перевірку до цього подання на межі домену.


Відмінний момент про приховування (або абстрагування) тих речей, які важко і можливо, змінити.
user949300

4

Ви думаєте про абстракцію, і це чудово. Однак речі, які ви перераховуєте, мені здаються, як правило, індивідуальними ознаками чогось більшого (звичайно, не все).

Я вважаю, що важливіше - забезпечити абстрагування, яке групує атрибути разом, і надає їм належний будинок, наприклад, клас Person, щоб клієнт-програміст не мав справу з декількома атрибутами, а з одним абстракцією більш високого рівня.

Коли ви маєте цю абстракцію, вам не обов'язково потрібні додаткові абстракції для атрибутів, які є лише значеннями. Клас Person може містити BirthDate як (примітивну) дату, поряд із PhoneNumbers як списком (примітивних) рядків, а Name - як (примітивний) рядок.

Якщо у вас є номер телефону з кодом форматування, тепер це два атрибути, і він би заслуговував класу на номер телефону (оскільки це, ймовірно, має і певну поведінку, наприклад, отримання номерів лише проти отримання форматованого номера телефону).

Якщо у вас є Ім'я як декілька атрибутів (наприклад, префікси / заголовки, перший, останній, суфікс), які б відповідали класу (так як зараз у вас є така поведінка, як отримання повного імені у вигляді рядка проти отримання менших фрагментів, заголовка тощо). .).


1

Ви повинні моделювати так, як вам потрібно, якщо ви просто хочете отримати деякі дані, ви можете використовувати типи примітивів, але якщо ви хочете зробити більше операцій над цими даними, їх слід моделювати в конкретних класах, наприклад (я згоден з @kiwiron у його коментарі) Віковий клас не має сенсу, але було б краще замінити його на клас народження, і ви помістите ці методи туди.

Перевага моделювання цих понять всередині класів полягає в тому, що ви застосовуєте багатство своєї моделі, наприклад, замість цього у вас є метод, який приймає будь-яку дату, користувач може заповнити її будь-якою датою, датою початку Другої світової війни або датою закінчення холодної війни, з цих дат, як і раніше, є дійсним датою народження. ви можете замінити його датою народження, тому в цьому випадку ви застосовуєте свій метод приймати дату народження, щоб не приймати жодної дати.

Для Firstname я не бачу великої користі, UniqueID теж мало, PhoneNumber трохи, ви можете попросити його вказати країну та регіон, наприклад, тому що, як правило, PhoneNumber відноситься до країн та регіонів, деякі оператори використовують попередньо визначені суфікси для класифікації мобільних пристроїв , Office ... числа, тож ви можете додати ці методи до цього класу, такі ж і для DomainName.

Для отримання більш детальної інформації рекомендую поглянути на цінні об’єкти в DDD (Design Driven Design).


1

Від цього залежить те, чи є у вас поведінка (яку потрібно підтримувати та / або змінювати), пов’язану з цими даними чи ні.

Коротше кажучи, якщо це лише фрагмент даних, який обкидається без особливої ​​поведінки, пов'язаної з ним, використовуйте примітивний тип або, для дещо складніших фрагментів даних, структуру даних (значною мірою без поведінки) (наприклад, клас із лише дітьми і сетери).

Якщо, з іншого боку, ви виявите, що для прийняття рішень ви перевіряєте або маніпулюєте цими даними в різних місцях вашого коду, місця часто не близькі один до одного - тоді перетворите їх на належний об'єкт, визначивши клас і розміщення відповідної поведінки всередині нього як методів. Якщо з якоїсь причини ви вважаєте, що не має сенсу визначати такий клас, альтернативою є консолідація такої поведінки в якомусь "службовому" класі, який працює на цих даних.


1

Ваші приклади виглядають набагато більше, як властивості або атрибути чогось іншого. Це означає, що було б цілком розумно починати саме з примітиву. У якийсь момент вам потрібно буде використовувати примітив, тому я зазвичай зберігаю складність, коли мені це справді потрібно.

Наприклад, скажімо, я роблю гру, і мені не потрібно турбуватися про старіння персонажів. Використання цілого числа для цього має ідеальний сенс. Вік може бути використаний у простих перевірках, щоб зрозуміти, чи дозволено персонажу щось робити чи ні.

Як зазначали інші, вік може бути складним предметом залежно від вашої мови. Якщо у вас є необхідність узгодити віки в різних місцевостях і т.д., то має сенс ввести Ageклас, який має DateOfBirthіLocale як властивості. Цей віковий клас також може розрахувати поточний вік у будь-який час.

Отже, є причини просувати примітив до класу, але вони дуже специфічні для застосування. Ось кілька прикладів:

  • У вас є спеціальна логіка перевірки, яку потрібно застосовувати всюди, де використовується тип інформації (наприклад, номери телефонів, адреси електронної пошти).
  • У вас є спеціальна логіка форматування, яка використовується для надання людині читання (наприклад, IP4 та IP6 адреси)
  • У вас є набір функцій, які всі відносяться до цього типу об'єкта
  • У вас є спеціальні правила порівняння подібних типів значень

В основному, якщо у вас є маса правил щодо того, як слід ставитися до значення, яке ви хочете послідовно використовувати протягом свого проекту, створіть клас. Якщо ви можете жити з простими значеннями, оскільки це не дуже важливо для функціональності, використовуйте примітив.


1

Зрештою, предмет є лише свідком того, що якийсь процес насправді запустився .

Примітив intозначає, що якийсь процес нулював 4 байти пам'яті, а потім перевернув біти, необхідні для представлення деякого цілого числа в діапазоні від -2,147,483,648 до 2,147,483,647; що не є сильним твердженням про поняття віку. Аналогічно, (недійсне)System.String означає, що деякі байти були виділені і біти перевернуті, щоб представити послідовність символів Unicode щодо деякого кодування.

Отже, вирішуючи, як представити об’єкт домену, слід подумати про процес, для якого він служить свідком. Якщо FirstNameдійсно має бути не порожній рядок, то він повинен виступати свідком того, що система запустила якийсь процес, який гарантує, що принаймні один символ, який не є пробілом, був створений у послідовності символів, переданих вам.

Аналогічно, Ageоб’єкт, ймовірно, повинен бути свідком того, що якийсь процес обчислював різницю між двома датами. З тих пірints як правило, не є результатом обчислення різниці між двома датами, вони ipso facto недостатні для представлення віків.

Отже, досить очевидно, що ви майже завжди хочете створити клас (який є лише програмою) для кожного об’єкта домену. То в чому проблема? Проблема полягає в тому, що C # (який я люблю) та Java вимагають занадто багато церемонії у створенні класів. Але ми можемо уявити альтернативні синтаксиси, які б зробили визначення об'єктів домену набагато простішим:

//hypothetical syntax
class Age = |start:date - end:date|.Years

(* ML-like syntax *)
type Age(x, y) = datediff(year, x, y)

//C#-like syntax with primary constructors and expression-bodied classes
class Age(DateTime x, DateTime y) => implicit operator int (Age a) => Abs((y - x).Years);

Наприклад, F # насправді має приємний синтаксис для цього у вигляді активних патерн :

//Impossible to produce an `Age` without first computing the difference of two dates
//But, any pair (tuple) of dates is implicitly converted to an `Age` when needed.

let (|Age|) (x, y) =  (date_diff y x).Days / 365

Справа в тому, що те, що ваша мова програмування робить громіздким визначення об’єктів домену, не означає, що це насправді є поганою ідеєю.


† Ми також називаємо ці речі пост-умовами , але я віддаю перевагу терміну "свідок", оскільки він слугує доказом того, що якийсь процес може і дійсно запуститися.


0

Подумайте, що ви отримуєте від використання класу порівняно з приматом. Якщо використання класу може отримати вас

  • перевірка
  • форматування
  • інші корисні методи
  • тип безпеки (тобто з PhoneNumberкласом, мені не можна буде призначити його для рядка або випадкової рядки (тобто "огірок"), де я очікую номер телефону)
  • будь-які інші переваги

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

Також усвідомлюйте, що іноді гарне іменування може просто обробити його (тобто рядок з ім'ям firstNameта створенням цілого класу, який просто загортає властивість рядка). Заняття - не єдиний спосіб вирішити речі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.