Към съдържанието
wannacode

Интерфейси

Проблем

Заплати и фактури

В края на месеца фирмата плаща две неща: заплатите на служителите и фактурите на доставчиците. Искаш един масив и един цикъл: на кого, колко, и накрая общата сума.

Масив 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 + " €");
Стъпка по стъпка, с думи
  1. Масивът е от тип Payable. Стрелките сочат три класа, но стигат само до договора им.
  2. total започва от 0.
  3. p сочи служителя: amountDue() връща заплатата, 2000.
  4. Мениджърът е служител, затова и той е платим. Заплата плюс бонус: 3500.
  5. Фактурата не е служител, но изпълнява същия договор: 340.
  6. Цикълът не знае класовете. Знае само, че всеки може да бъде платен.

Пробвай

Каква е сумата?

Какво ще отпечата програмата? Класовете са от горната карта.

Payable p = new Manager("Мария", 3000, 500);
System.out.println(p.amountDue());
Отговор

Идеята

Един родител, много договори

Счетоводството иска фактурите и в 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 {

Родителят е един, договорите са много. Така клас с вече зает родител пак може да влезе в общ масив с напълно различни класове.

Пробвай

Намери грешката

Тази фактура не се компилира. На кой ред е грешката?

Избери реда с грешката.

Пробвай

Кое липсва?

class Invoice  Payable {
    // ...
}

Идеята

Абстрактен клас или интерфейс?

И двете са родители, от които не се правят обекти, и двете задължават наследниците. Разликата е в това какво казват:

Абстрактен клас Интерфейс
Казва „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.5

3. Танц. Интерфейс 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[].

Тест

Провери се

1. Колко интерфейса може да реализира един клас?
2. Компилира ли се new Payable()?
3. Класът реализира метод на интерфейса без public. Какво става?
4. Как се казва в Java, че клас реализира интерфейс?
5. Служители и фактури трябва да се плащат в един цикъл. Те нямат общ код. Какво е по-подходящо?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·