Интерфейси
Проблем
Заплати и фактури
В края на месеца фирмата плаща две неща: заплатите на служителите и фактурите на доставчиците. Искаш един масив и един цикъл: на кого, колко, и накрая общата сума.
Масив Employee[] не става: фактурата не е служител. Да я накараш да наследи Employee е грешно. Изречението „фактурата е служител“ не е вярно, а тя ще получи и име, и заплата, и повишение.
Общ родител Payment също не става. Manager вече има родител, Employee, а класът може да има само един. Служителите и фактурите нямат общо нищо, освен че могат да бъдат платени.
Идеята
Интерфейс: договор „мога да“
Интерфейсът е списък с методи без тяло. Той казва какво трябва да може класът, но не и как:
interface Payable { int amountDue();
String payee();}class Invoice implements Payable { private String supplier; private int amount;
public Invoice(String supplier, int amount) { this.supplier = supplier; this.amount = amount; }
@Override public int amountDue() { return amount; }
@Override public String payee() { return supplier; }}
Invoice power = new Invoice("Доставчик на ток", 340);System.out.println(power.payee() + ": " + power.amountDue() + " €");Доставчик на ток: 340 €Интерфейсът се реализира с implements, а не с extends. @Override може да се пише и тук: компилаторът проверява, че методът наистина е от интерфейса.
Методите на интерфейса са винаги public. Те са обещание към всички, затова и в класа трябва да са public.
Виж го
Един цикъл за всичко, което се плаща
Служителят също приема договора. Неговата сума е месечната заплата:
class Employee implements Payable { // ... name, salary, конструкторът, monthlyPay()
@Override public int amountDue() { return monthlyPay(); }
@Override public String payee() { return name; }}Сега и служителите, и фактурите са Payable. Интерфейсът е тип: може да е тип на променлива, на параметър, на масив.
Payable[] payments = { new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Invoice("Доставчик на ток", 340), new Invoice("Наемодател", 1800)};int total = 0;for (Payable p : payments) { System.out.println(p.payee() + ": " + p.amountDue() + " €"); total += p.amountDue();}System.out.println("Общо: " + total + " €");Иван: 2000 €Мария: 3500 €Доставчик на ток: 340 €Наемодател: 1800 €Общо: 7640 €Мениджърът не пише нищо ново. Той наследява от Employee и договора, и amountDue(). А amountDue() вика monthlyPay(), който мениджърът е презаписал. Затова Мария получава 3500 €.
Payable[] payments = { new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Invoice("Доставчик на ток", 340)};int total = 0;for (Payable p : payments) { total += p.amountDue();}System.out.println("Общо: " + total + " €");Стъпка по стъпка, с думи
- Масивът е от тип Payable. Стрелките сочат три класа, но стигат само до договора им.
- total започва от 0.
- p сочи служителя: amountDue() връща заплатата, 2000.
- Мениджърът е служител, затова и той е платим. Заплата плюс бонус: 3500.
- Фактурата не е служител, но изпълнява същия договор: 340.
- Цикълът не знае класовете. Знае само, че всеки може да бъде платен.
Пробвай
Каква е сумата?
Какво ще отпечата програмата? Класовете са от горната карта.
Payable p = new Manager("Мария", 3000, 500);System.out.println(p.amountDue());Manager е Employee, а Employee реализира договора. Затова и мениджърът го реализира, без да го пише. Извикването отива в презаписания метод на мениджъра: 3000 + 500.
Идеята
Един родител, много договори
Счетоводството иска фактурите и в CSV формат: по един ред „доставчик;сума“. Това е втори договор. Класът може да реализира колкото интерфейса иска, разделени със запетая:
interface Exportable { String toCsv();}
class Invoice implements Payable, Exportable { // ... както преди
@Override public String toCsv() { return supplier + ";" + amount; }}Exportable[] forAccounting = { new Invoice("Доставчик на ток", 340), new Invoice("Наемодател", 1800) };for (Exportable e : forAccounting) { System.out.println(e.toCsv());}Доставчик на ток;340Наемодател;1800Ако класът има и родител, и интерфейси, родителят е пръв:
class Manager extends Employee implements Exportable {Родителят е един, договорите са много. Така клас с вече зает родител пак може да влезе в общ масив с напълно различни класове.
Пробвай
Намери грешката
Избери реда с грешката.
public. Методите на интерфейса са обещани на всички, а без модификатор методът е скрит. Компилаторът казва: „attempting to assign weaker access privileges; was public“.Пробвай
Кое липсва?
class Invoice Payable {
// ...
}implements: „реализира“. За родителски клас е extends, за интерфейс е implements.Идеята
Абстрактен клас или интерфейс?
И двете са родители, от които не се правят обекти, и двете задължават наследниците. Разликата е в това какво казват:
| Абстрактен клас | Интерфейс | |
|---|---|---|
| Казва | „X е Y“ | „X може да“ |
| Колко на клас | един | колкото искаш |
| Полета и конструктор | да | не |
| Общ код | да | почти не |
- Абстрактен клас, когато наследниците са един вид и имат общ код и данни: фигурите имат име и описание, служителите имат име и заплата.
- Интерфейс, когато класовете са различни, а им трябва обща способност: служител и фактура „могат да бъдат платени“, птица и самолет „могат да летят“.
Employee е родител на мениджъра и стажанта и същевременно реализира Payable.По-точно, за любопитните
- От Java 8 интерфейсът може да има метод по подразбиране (
default), с тяло. Класовете го получават наготово, а могат и да го презапишат. Така в интерфейс се добавя нов метод, без да се чупят класовете, които вече го реализират:
interface Flyable { void fly();
default void glide() { System.out.println("Плъзга се във въздуха"); }}
class Bird implements Flyable { @Override public void fly() { System.out.println("Птицата маха с крила"); }}
class Plane implements Flyable { @Override public void fly() { System.out.println("Самолетът излита"); }}
Flyable[] flyers = { new Bird(), new Plane() };for (Flyable f : flyers) { f.fly(); f.glide();}Птицата маха с крилаПлъзга се във въздухаСамолетът излитаПлъзга се във въздуха- Интерфейсът не може да има полета на обекта. Може да има константи: всяко поле в него е
public static final.
Упражнение
Договори
Реши задачите в редактора си.
1. Клаксон. Интерфейс Honkable с метод honk(). SimpleCar от модул „Класове“ го реализира.
Бип-бип!2. Подробности. Интерфейс Printable с метод printDetails(). Реализирай го в Person, а Student (наследник на Person) да го презапише и да добави успеха. Сложи двамата в масив Printable[].
Име: Георги, възраст: 45Име: Елена, възраст: 19Среден успех: 5.53. Танц. Интерфейс Danceable с метод dance(). DanceMove (име и продължителност) го реализира.
Танцувам салса 30 секунди4. Диджей. Клас Song със заглавие. Интерфейс Playable с метод play(). DJ го реализира и има метод playSong(Song song), който запомня песента и я пуска. Ако още няма песен, play() казва това.
Няма избрана песенПускам 'Bohemian Rhapsody'5. Езерото. Интерфейси Swimmable с swim() и Flyable с fly(). Fish плува. Duck и плува, и лети.
Рибата плува.Патицата плува.Патицата лети.Още задачи
- Отстъпка. Интерфейс Discountable с метод, който връща цената след отстъпка. Реализирай го в
Product(10% отстъпка) и вSubscription(25% отстъпка за студенти). Сметни сумата на количка с продукт за 50 € и студентски абонамент за 12 €: 45 + 9 = 54 €. - CSV за всички. Накарай и
Employeeда реализира Exportable, с ред „име;месечна заплата“. Сложи служители и фактури в един масив Exportable[].
Тест