Lab 09
Generics
List<T>, Queue<T>, constraints
Лаба 09 — Generics (Узагальнені типи)
Мета
Навчитись використовувати List<T> замість масивів з ручним лічильником, і самостійно писати generic класи з параметром типу <T>. Побачити, як один клас може працювати з різними типами без дублювання коду.
Контекст
Після восьми лаб система працює, але з обмеженнями: PatientManager має фіксований масив Patient[100] і вручну керує лічильником. Remove() вимагає зсуву всіх елементів. Це ті самі "навмисні обмеження" що були в Lab 03. Настав час замінити їх на List<T>.
Крім того, клініка потребує нову функціональність — чергу очікування: пацієнти приходять, стають у чергу, і приймаються по порядку. Це окрема функція, яка природньо виражається через Queue<T>.
Гілка
git checkout main
git pull
git checkout -b feature/genericsЗавдання 1 — List\: прощаємось з масивом і лічильником ⭐
Умова
PatientManager зберігає пацієнтів у Patient[] _patients з ручним int _count. Через це:
- є штучне обмеження (
MaxPatients = 100) Remove()потребує ручного зсуву елементівGetAll(),FindByName(),FindByBloodType()використовують двопрохідний паттерн
Заміни внутрішнє сховище на List<Patient>. Зовнішній API (Add, FindById, DisplayAll, Remove тощо) не змінюється — тільки внутрішня реалізація.
Що зміниться
- Поле
_patients— зPatient[]наList<Patient>, поле_countі константаMaxPatientsзникають Count— тепер_patients.CountAdd()— більше не перевіряє ліміт, використовує_patients.Add(...)Remove()— замість ручного зсуву використовує_patients.RemoveAt(i)FindByName(),FindByBloodType()— замість двопрохідного паттерну: наповнюй проміжнийList<Patient>і в кінці виклич.ToArray()GetAll()— одна пряма операція замість циклу
Підказки
List<T>— це динамічний масив зі стандартної бібліотеки. Розмір зростає автоматично при додаванні..Add(item)— додає в кінець..RemoveAt(index)— видаляє за індексом і зсуває решту автоматично..Count— кількість елементів (аналог_count).[i]— доступ за індексом (як у масиві)..ToArray()— повертає звичайний масивT[]зі всіх елементівList<T>.- Конструктор без параметрів
new List<Patient>()— порожній список. Абоnew List<Patient>(capacity)якщо знаєш приблизний розмір.
Що перевірити
Після змін поведінка системи не повинна змінитись. Запусти dotnet run — меню 1. Пацієнти працює так само, але тепер немає обмеження на 100 пацієнтів.
Адаптація до вашого домену
| Клініка | Готель | Ресторан | Університет | Прокат авто | Бібліотека | Спортзал |
|---|---|---|---|---|---|---|
PatientManager |
GuestManager |
CustomerManager |
StudentManager |
ClientManager |
ReaderManager |
MemberManager |
Patient[] → List<Patient> |
Guest[] → List<Guest> |
Customer[] → List<Customer> |
Student[] → List<Student> |
Client[] → List<Client> |
Reader[] → List<Reader> |
Member[] → List<Member> |
_count → _patients.Count |
_count → _guests.Count |
_count → _customers.Count |
_count → _students.Count |
_count → _clients.Count |
_count → _readers.Count |
_count → _members.Count |
Коміт
git add src/Managers/PatientManager.cs
git commit -m "Lab09 Task1: replace Patient[] array with List<Patient> in PatientManager"Завдання 2 — WaitingQueue\: власний generic клас ⭐⭐
Умова
Клініці потрібна черга очікування. Пацієнти приходять і стають у чергу (FIFO: перший прийшов — перший приймається). Це класична структура Queue<T> — але нам потрібна обгортка з зрозумілим API і захистом від помилок.
Створи generic клас WaitingQueue<T> у src/Models/WaitingQueue.cs.
Що реалізувати
Клас WaitingQueue<T> — обгортка над Queue<T> з такими членами:
int Count— кількість у черзі (тільки читання)bool IsEmpty— чи порожня чергаvoid Enqueue(T item)— додати в кінецьT Dequeue()— прийняти першого (видаляє з черги); якщо порожня — кинутиInvalidOperationExceptionT Peek()— подивитись хто перший (не видаляє); якщо порожня — кинутиInvalidOperationExceptionT[] ToArray()— поточний стан черги у вигляді масиву (для виводу)
Підказки
- Generic клас оголошується як
public class WaitingQueue<T>. ВикористовуйTскрізь де раніше писав би конкретний тип. Queue<T>— стандартна колекція FIFO.Enqueue— додати,Dequeue— взяти перший,Peek— подивитись на перший.Queue<T>сама кидаєInvalidOperationExceptionприDequeue/Peekна порожній черзі — але явна перевірка черезIsEmptyдає зрозуміліше повідомлення.- Параметр
<T>не накладає жодних обмежень —WaitingQueue<Patient>,WaitingQueue<Doctor>,WaitingQueue<string>— все компілюється.
📖 Queue<T> — Microsoft Docs
📖 Generic classes — Microsoft Docs
Що перевірити
Склади кілька рядків тесту в тимчасовому місці або прямо в Program.cs на початку:
WaitingQueue<string> q = new WaitingQueue<string>();
q.Enqueue("A"); q.Enqueue("B"); q.Enqueue("C");
// q.Count == 3, q.Peek() == "A", q.Dequeue() == "A", q.Count == 2Переконайся що Dequeue на порожній черзі кидає виняток.
Адаптація до вашого домену
| Клініка | Готель | Ресторан | Університет | Прокат авто | Бібліотека | Спортзал |
|---|---|---|---|---|---|---|
WaitingQueue<Patient> |
WaitingQueue<Guest> |
WaitingQueue<Customer> |
WaitingQueue<Student> |
WaitingQueue<Client> |
WaitingQueue<Reader> |
WaitingQueue<Member> |
| черга на прийом | черга на заїзд | черга на столик | черга на зарахування | черга на авто | черга на книгу | черга до тренера |
Enqueue / Dequeue / Peek |
Enqueue / Dequeue / Peek |
Enqueue / Dequeue / Peek |
Enqueue / Dequeue / Peek |
Enqueue / Dequeue / Peek |
Enqueue / Dequeue / Peek |
Enqueue / Dequeue / Peek |
Коміт
git add src/Models/WaitingQueue.cs
git commit -m "Lab09 Task2: add generic WaitingQueue<T> over Queue<T>"Завдання 3 — Черга в системі: нове меню ⭐⭐⭐
Умова
WaitingQueue<T> готова — тепер підключи її до реальної клініки. Додай чергу пацієнтів у Clinic і новий пункт меню "6. Черга — очікування, прийом".
Що реалізувати
Clinic.cs — нова властивість:
public WaitingQueue<Patient> WaitingRoom { get; }Ініціалізуй у конструкторі: WaitingRoom = new WaitingQueue<Patient>().
Program.cs — новий пункт у головному меню та функція WaitingRoomMenu(Clinic clinic) з чотирма діями:
- Додати пацієнта до черги (вибрати з існуючих пацієнтів за ID)
- Прийняти першого (
Dequeue) — вивести ім'я і кількість що залишились - Хто перший? (
Peek) — показати без видалення - Переглянути всю чергу — вивести список з нумерацією
Для дій 2 і 3 використовуй try/catch на InvalidOperationException.
Підказки
WaitingRoomзберігає об'єктиPatient— тому можна одразу виводитиpatient.FullName.clinic.WaitingRoom.ToArray()— отримати масив для виводу переліку черги.Queue<T>гарантує порядок FIFO — порядок виводу вToArray()відповідає порядку додавання.
Що перевірити
Запусти dotnet run. Відкрий 6. Черга. Додай кількох пацієнтів. Виклич "Прийняти першого" двічі — переконайся що порядок правильний (FIFO). Спробуй "Прийняти" з порожньої черги — повинно вивести повідомлення, а не крашнутись.
Адаптація до вашого домену
| Клініка | Готель | Ресторан | Університет | Прокат авто | Бібліотека | Спортзал |
|---|---|---|---|---|---|---|
Clinic.WaitingRoom |
Hotel.CheckInQueue |
Restaurant.DiningQueue |
University.EnrollmentQueue |
CarRental.RentalQueue |
Library.BorrowQueue |
GymCenter.TrainingQueue |
6. Черга — очікування, прийом |
6. Черга — реєстрація |
6. Черга — на столик |
6. Черга — зарахування |
6. Черга — на авто |
6. Черга — на книгу |
6. Черга — до тренера |
Коміт
git add src/Clinic.cs src/Program.cs
git commit -m "Lab09 Task3: add WaitingRoom to Clinic, add menu item 6 Черга очікування"Завдання 4 — Repository\: generic CRUD з обмеженням типу ⭐⭐⭐⭐
Умова
WaitingQueue<T> не має обмежень — до неї можна додати будь-який тип. Іноді generic клас повинен гарантувати що T має певні властивості. Наприклад, Repository<T> потрібен метод GetById(int id) — але для цього він повинен знати що у T є поле Id.
Вирішення: constraint where T : IIdentifiable.
Що реалізувати
Interfaces/IIdentifiable.cs — новий інтерфейс:
public interface IIdentifiable
{
int Id { get; }
}Models/Patient.cs, Models/Doctor.cs, Models/Appointment.cs — додай : IIdentifiable до оголошення класу. Поле Id вже є — реалізація автоматична.
Managers/Repository.cs — generic клас Repository<T> where T : IIdentifiable з методами:
void Add(T item)— додатиT GetById(int id)— знайти за Id (абоdefault!якщо не знайдено)T[] GetAll()— всі елементиbool Remove(int id)— видалити за Idint Count— кількість
Внутрішньо використовуй List<T>.
Ключові питання
- Що дає
where T : IIdentifiable? Що буде якщо прибрати constraint і спробувати викликатиitem.Id? - Чим
Repository<T>відрізняється відPatientManager? Коли є сенс використовувати один, коли інший? - Спробуй:
Repository<Patient> repo = new Repository<Patient>(). Чи компілюється? АRepository<string>?
Підказки
where T : IIdentifiable— компілятор дозволяє звертатись доitem.Idвсередині класу, бо гарантовано що уTє цей член.default!— повертаєnullдля reference types, підходить як "не знайдено" аналогічно доnull!в існуючому коді.Repository<T>— не замінюєPatientManager. Це окремий generic інструмент.PatientManagerмістить специфічну логіку (пошук за ім'ям, статистика) якуRepositoryне знає.
📖 Generic constraints — Microsoft Docs
📖 default keyword — Microsoft Docs
Що перевірити
У Program.cs (тимчасово, для перевірки):
Repository<Patient> repo = new Repository<Patient>();
// додай кількох пацієнтів через repo.Add(...)
// спробуй repo.GetById(id), repo.GetAll(), repo.Remove(id)Переконайся що Repository<string> не компілюється — string не реалізує IIdentifiable.
Коміт
git add src/Interfaces/IIdentifiable.cs src/Managers/Repository.cs src/Models/Patient.cs src/Models/Doctor.cs src/Models/Appointment.cs
git commit -m "Lab09 Task4: add IIdentifiable, Repository<T> where T : IIdentifiable"Перевірка перед здачею
cd src
dotnet build
dotnet run-
1. Пацієнти— поведінка не змінилась, але немає ліміту на кількість -
6. Черга— з'явився новий пункт у головному меню - Додати 3 пацієнтів у чергу → прийняти двох → у черзі 1
- "Прийняти" з порожньої черги → повідомлення про помилку, програма не крашиться
-
Repository<Patient>компілюється;Repository<string>— ні -
WaitingQueue<string>,WaitingQueue<int>— обидва компілюються (без constraint)
Питання для самоперевірки
- В чому різниця між
List<T>іT[]? Коли перевага у масиву, коли уList<T>? - Що значить
<T>в оголошенні класу? Хто вказує конкретний тип — і коли? - Навіщо
where T : IIdentifiable? Що дає constraint порівняно зWaitingQueue<T>без нього? - Чому
WaitingQueue<T>не має constraint, аRepository<T>має? В чому принципова різниця між ними? - FIFO vs LIFO:
Queue<T>абоStack<T>— для черги очікування який підходить і чому? PatientManagerтепер використовуєList<Patient>. Чи є сенс замінити весьPatientManagerнаRepository<Patient>? Що б втратилось?
Злиття
git checkout main
git merge --no-ff feature/generics -m "Merge feature/generics: Lab09 Generics — List<T>, WaitingQueue<T>, Repository<T>"
git pushНаступна лаба:
git checkout -b feature/iterators—IEnumerable<T>,yield return,IComparable<T>.