Що ви опануєте
Ви навчитеся представляти предметну ідентичність, контролювати зміни стану й працювати з колекцією сутностей. Приклад — конкретний пристрій із можливістю бронювання. Пояснимо Guid, властивості з private set, List і пошук до використання цих засобів у моделях складнішої поведінки.
Ідентичність та Guid
Guid — 128-бітний ідентифікатор, представлений типом System.Guid. Guid.NewGuid() створює новий ідентифікатор із дуже малою практичною ймовірністю випадкового збігу; сховище все одно має забезпечувати потрібне правило унікальності. Guid.Empty є нульовим значенням. ID дозволяє розрізняти два пристрої з однаковою назвою.
Ідентифікатор не є секретом і сам не надає права доступу. Знання ID чужого бронювання не повинно дозволяти його скасувати без перевірки користувача. Формат ID також не замінює предметної перевірки існування. Дані можуть містити коректно записаний Guid, якому жодна сутність не відповідає.
Інваріант та операції
Інваріант пристрою: ID непорожній, назва непорожня, стан належить дозволеному набору. Для початку використаємо Available і Reserved. Enum задає іменовані цілі константи. Зовнішній код не повинен довільно встановлювати стан; операція Reserve перевіряє поточний стан і створює лише допустимий перехід.
using System;
using System.Collections.Generic;
var equipment = new Equipment(Guid.NewGuid(), "Мікроскоп");
var items = new List<Equipment> { equipment };
Console.WriteLine(items[0].Reserve()); // True.
Console.WriteLine(items[0].Reserve()); // False.
Console.WriteLine(items[0].State); // Reserved.
public enum EquipmentState { Available, Reserved }
public sealed class Equipment
{
public Guid Id { get; }
public string Name { get; }
public EquipmentState State { get; private set; } = EquipmentState.Available;
public Equipment(Guid id, string name)
{
if (id == Guid.Empty) throw new ArgumentException("Порожній ID");
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Назва");
Id = id;
Name = name.Trim();
}
public bool Reserve()
{
if (State != EquipmentState.Available) return false;
State = EquipmentState.Reserved;
return true;
}
}
private set залишає читання стану публічним і запис усередині класу. Повторна операція повертає false без зміни. У цьому навчальному прикладі власник бронювання ще не врахований; наступне розширення має додати його як частину контракту. Не приписуйте класу гарантії багатокористувацького сховища, яких у коді немає.
Колекції
List<T> зберігає впорядковану змінювану послідовність елементів типу T. T є параметром узагальненого типу. Індекс починається з нуля; доступ поза межами спричиняє виняток. Dictionary<TKey,TValue> пов’язує ключ зі значенням і корисний для пошуку за ID. Додавання дубліката через Add відхиляється, а присвоєння через індекс може замінити існуючий запис.
Копіювання List створює нову колекцію посилань на ті самі об’єкти, якщо елементи — класи. Зміна стану Equipment буде видима через обидва списки. Потрібно розрізняти незалежність контейнера й незалежність сутностей. Для звіту можна повертати окремі незмінювані значення з потрібними полями.
Пошук і відсутність
Пошук має визначати, що відбувається, якщо ID не знайдено. Варіанти — nullable результат, TryGetValue або окремий об’єкт результату. Для словника TryGetValue повертає bool і записує значення в out параметр тільки за успішної перевірки. Так читач бачить перевірку існування до використання.
var byId = new Dictionary<Guid, Equipment> { [equipment.Id] = equipment };
if (byId.TryGetValue(equipment.Id, out var found))
Console.WriteLine(found.Name);
Цей фрагмент додають до top-level частини попереднього прикладу до оголошення типів. out var оголошує змінну для результату. Конкретний предметний метод, наприклад резервування за ID, додатково перевірятиме права й допустимість переходу. Знайдений запис ще не означає дозволену дію.
Агрегат і межа узгодженості
Агрегат об’єднує сутності й значення, правила яких підтримуються через одну визначену межу. Це поняття використовують, коли кілька частин мають змінюватися узгоджено. Наприклад, бронювання й доступність пристрою можуть потребувати атомарного переходу. Вибір межі залежить від інваріанта та способу збереження.
Клас із List не забезпечує автоматичної транзакції для кількох користувачів. Два запити можуть прочитати один Available перед записом. Узгодженість у сховищі потребує обмежень, версійності або транзакційного механізму. Модель описує правило, архітектура й база забезпечують його виконання за відповідних умов.
Типові помилки та практика
Помилки: порівняння сутностей лише за назвою, Guid як засіб авторизації, публічний set стану й мовчазна заміна дубліката словника. Інша проблема — повернення всієї змінюваної колекції користувачу, який може обійти операції моделі. Надавайте вузький контракт читання й контролюйте зміни в потрібній межі.
Практичний аналіз
Додайте ім’я власника бронювання як окремий ID користувача й визначте правило скасування. Чи можна зняти чужу резервацію? Що має відбуватися після повторного Reserve? Складіть тести й поясніть, які з них потребують одного об’єкта, а які — перевірки взаємодії зі сховищем.
Ідентичність, колекції та знімок стану
Guid є значенням ідентифікатора; Guid.NewGuid створює новий id з дуже малою ймовірністю випадкового збігу. Guid.Empty має всі нульові біти та може служити окремим технічним маркером, якщо це погоджено контрактом. Guid не містить назви чи прав доступу до об’єкта, тому перевірку ролей задають окремо.
List<T> зберігає послідовність елементів, Dictionary<TKey,TValue> — відповідність ключів значенням. Для пошуку пристрою за id словник явно представляє унікальність ключа; Add повторного ключа завершується помилкою. TryGetValue дозволяє перевірити наявність без використання винятку як звичайного шляху для відсутнього id.
Якщо повернути клієнту внутрішній List, він може додати або видалити елементи поза правилами сервісу. IReadOnlyList обмежує доступ через цей інтерфейс, але не робить кожен об’єкт незмінним і не гарантує незмінність початкової колекції. Для стабільного знімка можна створити окрему копію; для вкладених змінюваних об’єктів потрібно визначити потрібну глибину копіювання.
Після переходу Available → Reserved визначте, які додаткові факти мають з’явитися: власник резервування, час та його id. Стан і дані повинні бути узгодженими. Якщо метод лише змінює enum, але не фіксує потрібний зв’язок, модель може не відповідати реальному процесу. Збережіть таблицю станів і для кожного покажіть обов’язкові атрибути. Це допомагає виявити неповні переходи до написання сховища.
Схема процесу
Guid розрізняє конкретні сутності.
Самоперевірка
Як обґрунтувати відповідь своїми словами?
Підсумок
Guid, колекції й предметні операції представляють ідентичність та поведінку моделі. Приватна зміна стану підтримує інваріант. Права доступу та багатокористувацька атомарність залишаються окремими визначеними контрактами.