Интерфейси
Проблем
Заплати и фактури
В края на месеца фирмата плаща две неща: заплатите на служителите и фактурите на доставчиците. Искаш един масив и един цикъл: на кого, колко, и накрая общата сума.
Масив Employee[] не става: фактурата не е служител. Да я накараш да наследи Employee е грешно. Изречението „фактурата е служител“ не е вярно, а тя ще получи и име, и заплата, и повишение.
Общ родител Payment също не става. Manager вече има родител, Employee, а класът може да има само един. Служителите и фактурите нямат общо нищо, освен че могат да бъдат платени.
Идеята
Интерфейс: договор „мога да“
Интерфейсът е списък с методи без тяло. Той казва какво трябва да може класът, но не и как:
interface IPayable{ int AmountDue();
string Payee();}class Invoice : IPayable{ public string Supplier { get; private set; } public int Amount { get; private set; }
public Invoice(string supplier, int amount) { Supplier = supplier; Amount = amount; }
public int AmountDue() { return Amount; }
public string Payee() { return Supplier; }}
Invoice power = new Invoice("Доставчик на ток", 340);Console.WriteLine(power.Payee() + ": " + power.AmountDue() + " €");Доставчик на ток: 340 €Интерфейсът се реализира със същото двоеточие като наследяването. Методите се пишат без override: няма какво да се презаписва, интерфейсът няма свои версии.
В C# имената на интерфейсите започват с I: IPayable, IComparable, IDisposable. Така от class Invoice : IPayable се вижда веднага, че двоеточието е за интерфейс.
Методите на интерфейса са винаги public. Те са обещание към всички, затова и в класа трябва да са public.
Виж го
Един цикъл за всичко, което се плаща
Служителят също приема договора. Неговата сума е месечната заплата:
class Employee : IPayable{ // ... Name, Salary, конструкторът, MonthlyPay()
public int AmountDue() { return MonthlyPay(); }
public string Payee() { return Name; }}Сега и служителите, и фактурите са IPayable. Интерфейсът е тип: може да е тип на променлива, на параметър, на масив.
IPayable[] payments ={ new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Invoice("Доставчик на ток", 340), new Invoice("Наемодател", 1800)};int total = 0;foreach (IPayable p in payments){ Console.WriteLine(p.Payee() + ": " + p.AmountDue() + " €"); total += p.AmountDue();}Console.WriteLine("Общо: " + total + " €");Иван: 2000 €Мария: 3500 €Доставчик на ток: 340 €Наемодател: 1800 €Общо: 7640 €Мениджърът не пише нищо ново. Той наследява от Employee и договора, и AmountDue(). А AmountDue() вика MonthlyPay(), който мениджърът е презаписал. Затова Мария получава 3500 €.
IPayable[] payments ={ new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Invoice("Доставчик на ток", 340)};int total = 0;foreach (IPayable p in payments){ total += p.AmountDue();}Console.WriteLine("Общо: " + total + " €");Стъпка по стъпка, с думи
- Масивът е от тип IPayable. Стрелките сочат три класа, но стигат само до договора им.
- total започва от 0.
- p сочи служителя: AmountDue() връща заплатата, 2000.
- Мениджърът е служител, затова и той е платим. Заплата плюс бонус: 3500.
- Фактурата не е служител, но изпълнява същия договор: 340.
- Цикълът не знае класовете. Знае само, че всеки може да бъде платен.
Пробвай
Каква е сумата?
Какво ще отпечата програмата? Класовете са от горната карта.
IPayable p = new Manager("Мария", 3000, 500);Console.WriteLine(p.AmountDue());Manager е Employee, а Employee реализира договора. Затова и мениджърът го реализира, без да го пише. Извикването отива в презаписания метод на мениджъра: 3000 + 500.
Идеята
Един родител, много договори
Счетоводството иска фактурите и в CSV формат: по един ред „доставчик;сума“. Това е втори договор. Класът може да реализира колкото интерфейса иска, разделени със запетая:
interface IExportable{ string ToCsv();}
class Invoice : IPayable, IExportable{ // ... както преди
public string ToCsv() { return Supplier + ";" + Amount; }}IExportable[] forAccounting = { new Invoice("Доставчик на ток", 340), new Invoice("Наемодател", 1800) };foreach (IExportable e in forAccounting){ Console.WriteLine(e.ToCsv());}Доставчик на ток;340Наемодател;1800Ако класът има и родител, и интерфейси, родителят е пръв:
class Manager : Employee, IExportableПървото име след двоеточието може да е клас. Всички останали са интерфейси.
Родителят е един, договорите са много. Така клас с вече зает родител пак може да влезе в общ масив с напълно различни класове.
Пробвай
Намери грешката
Избери реда с грешката.
public. Методите на интерфейса са обещани на всички, а без модификатор методът е скрит. Компилаторът казва, че Invoice не реализира IPayable.AmountDue(), защото методът не е public.Пробвай
Кое липсва?
class Invoice IPayable
{
// ...
}IPayable с I отпред показва, че е интерфейс.Идеята
Абстрактен клас или интерфейс?
И двете са родители, от които не се правят обекти, и двете задължават наследниците. Разликата е в това какво казват:
| Абстрактен клас | Интерфейс | |
|---|---|---|
| Казва | „X е Y“ | „X може да“ |
| Колко на клас | един | колкото искаш |
| Полета и конструктор | да | не |
| Общ код | да | почти не |
- Абстрактен клас, когато наследниците са един вид и имат общ код и данни: фигурите имат име и описание, служителите имат име и заплата.
- Интерфейс, когато класовете са различни, а им трябва обща способност: служител и фактура „могат да бъдат платени“, птица и самолет „могат да летят“.
Employee е родител на мениджъра и стажанта и същевременно реализира IPayable.По-точно, за любопитните
- От C# 8 интерфейсът може да има метод по подразбиране, с тяло. Класовете го получават наготово, а могат и да напишат свой. Така в интерфейс се добавя нов метод, без да се чупят класовете, които вече го реализират. Такъв метод се вика през променлива от типа на интерфейса (
IFlyable f), а не презBird:
interface IFlyable{ void Fly();
void Glide() { Console.WriteLine("Плъзга се във въздуха"); }}
class Bird : IFlyable{ public void Fly() { Console.WriteLine("Птицата маха с крила"); }}
class Plane : IFlyable{ public void Fly() { Console.WriteLine("Самолетът излита"); }}
IFlyable[] flyers = { new Bird(), new Plane() };foreach (IFlyable f in flyers){ f.Fly(); f.Glide();}Птицата маха с крилаПлъзга се във въздухаСамолетът излитаПлъзга се във въздуха- Интерфейсът не може да има полета. Може да има свойства, но само като обещание:
int Amount { get; }казва „всеки клас ще има такова свойство“.
Упражнение
Договори
Реши задачите в редактора си.
1. Клаксон. Интерфейс IHonkable с метод Honk(). SimpleCar от модул „Класове“ го реализира.
Бип-бип!2. Подробности. Интерфейс IPrintable с метод PrintDetails(). Реализирай го в Person, а Student (наследник на Person) да го презапише и да добави успеха. Сложи двамата в масив IPrintable[].
Име: Георги, възраст: 45Име: Елена, възраст: 19Среден успех: 5.53. Танц. Интерфейс IDanceable с метод Dance(). DanceMove (име и продължителност) го реализира.
Танцувам салса 30 секунди4. Диджей. Клас Song със заглавие. Интерфейс IPlayable с метод Play(). DJ го реализира и има метод PlaySong(Song song), който запомня песента и я пуска. Ако още няма песен, Play() казва това.
Няма избрана песенПускам 'Bohemian Rhapsody'5. Езерото. Интерфейси ISwimmable с Swim() и IFlyable с Fly(). Fish плува. Duck и плува, и лети.
Рибата плува.Патицата плува.Патицата лети.Още задачи
- Отстъпка. Интерфейс IDiscountable с метод, който връща цената след отстъпка. Реализирай го в
Product(10% отстъпка) и вSubscription(25% отстъпка за студенти). Сметни сумата на количка с продукт за 50 € и студентски абонамент за 12 €: 45 + 9 = 54 €. - CSV за всички. Накарай и
Employeeда реализира IExportable, с ред „име;месечна заплата“. Сложи служители и фактури в един масив IExportable[].
Тест