Lab 07
Інтерфейси
IPayable, ICancellable, ISchedulable
Лаба 07 — Interfaces (Інтерфейси)
Мета
Навчитись оголошувати інтерфейси, реалізовувати їх у класах та писати код, що залежить від контракту, а не від конкретного типу.
Контекст
Після Lab 06 система вміє зберігати медичні записи. Але оплата прийомів досі не відстежується — немає поняття "оплачено / не оплачено", немає суми, немає можливості скасування через інтерфейс. Ця лаба додає фінансовий блок: інтерфейси IPayable, ICancellable, ISchedulable та новий розділ меню "Рахунки".
Гілка
git checkout main
git pull
git checkout -b feature/interfacesЗавдання 1 — IPayable та оплата записів ⭐
Умова
Клініка хоче відстежувати оплату прийомів. Кожен запис має вартість і статус оплати.
Що реалізувати
Interfaces/IPayable.cs — новий файл:
namespace YourApp.Interfaces;
public interface IPayable
{
decimal GetCost();
bool IsPaid { get; }
void MarkPaid();
}Models/Appointment.cs — клас реалізує IPayable:
public class Appointment : IPayableДодати:
- Приватне поле
bool _isPaid decimal GetCost()— повертає вартість (наприклад,(decimal)DurationMinutes * 10m)bool IsPaid => _isPaid;void MarkPaid()— якщо не скасовано, встановлює_isPaid = true
Managers/AppointmentManager.cs — додати метод:
public Appointment[] GetAll() { ... }Повертає копію всіх записів у масиві.
Підказки
- Інтерфейс — це контракт без реалізації. Клас що його реалізує зобов'язаний надати тіло кожного члена.
decimal(неdouble) — для грошових розрахунків. Літерал:10m.MarkPaid()не повинен дозволяти оплату скасованого запису.- Interfaces — C# docs
Адаптація до вашого домену
| Клініка | Готель | Ресторан | Університет | Прокат авто | Бібліотека | Спортзал |
|---|---|---|---|---|---|---|
Appointment реалізує IPayable |
Booking реалізує IPayable |
TableReservation реалізує IPayable |
Enrollment реалізує IPayable |
Rental реалізує IPayable |
BookLoan реалізує IPayable |
Session реалізує IPayable |
GetCost() = DurationMinutes * 10m |
GetCost() = StayNights * RoomRate |
GetCost() = фіксована ставка |
GetCost() = CourseFee |
GetCost() = RentalDays * DailyRate |
GetFine() = OverdueDays * DailyFine |
GetCost() = DurationMinutes * Rate |
IsPaid / MarkPaid() |
IsPaid / MarkPaid() |
IsPaid / MarkPaid() |
IsPaid / MarkPaid() |
IsPaid / MarkPaid() |
IsPaid / MarkPaid() |
IsPaid / MarkPaid() |
Коміт
git add src/Interfaces/IPayable.cs src/Models/Appointment.cs src/Managers/AppointmentManager.cs
git commit -m "Lab07 Task1: add IPayable, implement in Appointment, add AppointmentManager.GetAll()"Завдання 2 — ICancellable та скасування через контракт ⭐⭐
Умова
Менеджер хоче скасувати всі прострочені записи одним викликом — незалежно від того, що це за об'єкт. Потрібен спільний контракт.
Що реалізувати
Interfaces/ICancellable.cs — новий файл:
namespace YourApp.Interfaces;
public interface ICancellable
{
bool IsCancelled { get; }
string CancellationReason { get; }
bool Cancel(string reason = "");
}Models/Appointment.cs — додати другий інтерфейс:
public class Appointment : IPayable, ICancellableДодати:
bool IsCancelled => Status == AppointmentStatus.Cancelled;string CancellationReason => IsCancelled ? Notes : "";- Метод
Cancel(string reason)вже існує — нічого не міняти.
Підказки
- Клас може реалізовувати кілька інтерфейсів — через кому:
class X : IA, IB. - Якщо метод вже є в класі з правильною сигнатурою — компілятор вважає його реалізацією інтерфейсу автоматично.
- Спробуй написати метод що приймає
ICancellable[]і скасовує всі де!IsCancelledта дата вже минула. Він не знає нічого проAppointment— тільки про контракт. - Multiple interfaces — C# docs
Адаптація до вашого домену
| Клініка | Готель | Ресторан | Університет | Прокат авто | Бібліотека | Спортзал |
|---|---|---|---|---|---|---|
Appointment реалізує ICancellable |
Booking реалізує ICancellable |
TableReservation реалізує ICancellable |
Enrollment реалізує ICancellable |
Rental реалізує ICancellable |
BookLoan реалізує ICancellable |
Session реалізує ICancellable |
Cancel(reason) |
Cancel(reason) |
Cancel(reason) |
Withdraw(reason) |
Cancel(reason) |
Cancel(reason) |
Cancel(reason) |
CancellationReason |
CancellationReason |
CancellationReason |
WithdrawalReason |
CancellationReason |
CancellationReason |
CancellationReason |
Коміт
git add src/Interfaces/ICancellable.cs src/Models/Appointment.cs
git commit -m "Lab07 Task2: add ICancellable, implement in Appointment"Завдання 3 — ISchedulable та BillingManager ⭐⭐⭐
Умова
Потрібно: (а) перевіряти чи лікар приймає в певний час через єдиний контракт; (б) зібрати всі неоплачені записи та порахувати борги.
Що реалізувати
Interfaces/ISchedulable.cs — новий файл:
namespace YourApp.Interfaces;
public interface ISchedulable
{
bool CanSchedule(DateTime at);
DateTime[] GetAvailableSlots(DateTime date, int slotCount);
}Models/Doctor.cs — реалізує ISchedulable:
public class Doctor : ISchedulableДодати:
bool CanSchedule(DateTime at) => Schedule.Contains(at.Hour);DateTime[] GetAvailableSlots(DateTime date, int slotCount)— повертає масив вільних слотів (цілі години в межахSchedule.Start..Schedule.End).
Managers/BillingManager.cs — новий файл. Приймає AppointmentManager у конструкторі.
Методи:
IPayable[] GetAllUnpaid()— всі записи де!IsPaid && !IsCancelledIPayable[] GetUnpaidByPatient(int patientId)— фільтр по пацієнтуdecimal GetTotalDebt()— сумаGetCost()по всіх неоплаченихdecimal GetPatientDebt(int patientId)— сума по пацієнтуbool PayAppointment(int appointmentId)— знаходить запис та викликаєMarkPaid()void DisplayUnpaid(IPayable[] items)— виводить список з сумами
Підказки
GetAllUnpaid()повертаєIPayable[]— неAppointment[]. Метод не "знає" що цеAppointment.GetTotalDebt()ітеруєIPayable[]та викликає.GetCost()— без жодного знання про конкретний тип.DisplayUnpaidможе використатиis Appointment aщоб показати деталі (пацієнт, лікар, дата), але це необов'язково — можна вивести лише суму через інтерфейс.- Interface as parameter type
Адаптація до вашого домену
| Клініка | Готель | Ресторан | Університет | Прокат авто | Бібліотека | Спортзал |
|---|---|---|---|---|---|---|
Doctor реалізує ISchedulable |
Staff реалізує ISchedulable |
Waiter реалізує ISchedulable |
Lecturer реалізує ISchedulable |
Manager реалізує ISchedulable |
Librarian реалізує ISchedulable |
Trainer реалізує ISchedulable |
CanSchedule(DateTime) |
CanCheckIn(DateTime) |
CanServe(DateTime) |
CanTeach(DateTime) |
CanHandle(DateTime) |
CanIssue(DateTime) |
CanTrain(DateTime) |
BillingManager |
BillingManager |
BillingManager |
BillingManager |
BillingManager |
FineManager |
BillingManager |
GetUnpaidByPatient |
GetUnpaidByGuest |
GetUnpaidByCustomer |
GetUnpaidByStudent |
GetUnpaidByClient |
GetUnpaidFinesByReader |
GetUnpaidByMember |
Коміт
git add src/Interfaces/ISchedulable.cs src/Models/Doctor.cs src/Managers/BillingManager.cs
git commit -m "Lab07 Task3: add ISchedulable, implement in Doctor, add BillingManager"Завдання 4 — Інтеграція та меню "Рахунки" ⭐⭐⭐
Умова
Підключити BillingManager до системи та показати роботу інтерфейсів через консоль.
Що реалізувати
Clinic.cs — додати:
public BillingManager Billing { get; }
// у конструкторі:
Billing = new BillingManager(Appointments);Program.cs — нове підменю "Рахунки":
── Рахунки ───────────────────
1. Борги пацієнта
2. Всі неоплачені записи
3. Оплатити запис
4. Загальний борг клініки
0. НазадТакож оновити головне меню — додати короткий опис кожного пункту через —:
║ 1. Пацієнти — реєстрація, пошук ║
║ 2. Лікарі — персонал, розклад ║
...
║ 5. Рахунки — оплата, борги ║Підказки
- У
BillingMenuтип змінної:IPayable[] unpaid = clinic.Billing.GetUnpaidByPatient(pId);— неAppointment[]. GetPatientDebtіDisplayUnpaid— два окремі виклики для одного пацієнта.- Порядок
catch: якщо додаєшtry/catch— спочатку конкретніший виняток.
Коміт
git add src/Clinic.cs src/Program.cs
git commit -m "Lab07 Task4: integrate BillingManager into Clinic, add BillingMenu and menu descriptions"Перевірка перед здачею
cd src
dotnet build
dotnet runПереконайтесь, що:
- Головне меню має описи через
—для кожного пункту - Пункт "5. Рахунки" присутній і відкриває підменю
- "Борги пацієнта" виводить список і суму
- "Оплатити запис" повертає підтвердження, після чого запис зникає зі списку неоплачених
- "Оплатити" вже оплачений запис — повідомлення про помилку, не крах
- "Загальний борг" рахується правильно (сума по всіх неоплачених)
-
IPayable[] items = clinic.Billing.GetAllUnpaid()компілюється — тип змінної інтерфейс, не клас -
BillingManager.GetTotalDebt()не містить жодногоis Appointment— тількиIPayable
Питання для самоперевірки
- Чим інтерфейс відрізняється від
abstract class? Коли обираєш одне, коли інше? - Чому
GetTotalDebt()приймаєIPayable[], а неAppointment[]? Що зміниться якщо завтраPrescriptionтеж стане платним? - Клас
BillingManagerне має жодногоimportнаAppointmentнапряму (тільки черезAppointmentManager). Чому це добре? - Що означає "реалізувати кілька інтерфейсів"? Чому C# не дозволяє успадкування від кількох класів, але дозволяє від кількох інтерфейсів?
IPayable[] items = new Appointment[3]— чому це компілюється? Що за механізм?- Спробуй написати метод
static decimal TotalCost(IPayable[] items)— де він може жити і чому він не залежить від жодного конкретного класу?
Злиття
git checkout main
git merge --no-ff feature/interfaces -m "Merge feature/interfaces: Lab07 Interfaces"
git pushНаступна лаба:
git checkout -b feature/polymorphism—RegularAppointment,UrgentAppointment,newkeyword,sealed.