ООП принципи
Накратко: Обектно-ориентираното програмиране стои на четири принципа: капсулация, наследяване, полиморфизъм и абстракция. Те пазят данните, пишат общото веднъж и заменят веригите от if-ове по вид на обекта.
Проблем
Заплати без принципи
В края на месеца фирмата пуска заплатите. Програмата е писана набързо: един клас с публични полета, ролята е текст, а заплатата се смята на две места. Методът monthlyPay прави фиша, а transfer превежда парите в банката.
class Employee { public String name; public String role; public int salary; public int bonus;
public Employee(String name, String role, int salary, int bonus) { this.name = name; this.role = role; this.salary = salary; this.bonus = bonus; }}static int monthlyPay(Employee e) { if (e.role.equals("мениджър")) { return e.salary + e.bonus; } else if (e.role.equals("стажант")) { return e.salary / 2; } return e.salary;}
static void transfer(Employee[] staff) { int total = 0; for (Employee e : staff) { int amount = e.salary; if (e.role.equals("мениджър")) { amount += e.bonus; } System.out.println("Превод: " + e.name + " " + amount + " €"); total += amount; } System.out.println("Общо: " + total + " €");}Днес счетоводството въвежда повишение за Иван. Във формуляра обаче е написано -10 вместо 10:
Employee[] staff = { new Employee("Иван", "служител", 2000, 0), new Employee("Мария", "мениджър", 3000, 500), new Employee("Ана", "стажант", 1600, 0)};int percent = -10;staff[0].salary += staff[0].salary * percent / 100;for (Employee e : staff) { System.out.println("Фиш: " + e.name + " " + monthlyPay(e) + " €");}transfer(staff);Фиш: Иван 1800 €Фиш: Мария 3500 €Фиш: Ана 800 €Превод: Иван 1800 €Превод: Мария 3500 €Превод: Ана 1600 €Общо: 6900 €Програмата не спира и не се оплаква. А в изхода има три пукнатини:
- Грешен запис. Заплатата на Иван падна на 1800 €. Полето е отворено за всеки ред в програмата и никой не пита дали -10% е повишение.
- Две копия на едно правило. Фишът на Ана казва 800 €, а банката ѝ превежда 1600 €. Правилото „стажантът взима половината“ е във фиша, а в превода са го забравили. Копието се е разминало с оригинала и никой не е разбрал.
- Нов вид, много поправки. Утре идва служител на хонорар. Трябва нов
else ifвъв фиша, в превода, в данъците, в отпуските, навсякъде, където някой пита за ролята. А всеки служител вече носи полеbonus, макар че бонус има само мениджърът.
Обектно-ориентираното програмиране е отговорът точно на тези три неща.
Идеята
Четирите принципа
Обектно-ориентираното програмиране (ООП) подрежда програмата около обекти: данни заедно с методите, които ги пипат. Стои на четири принципа. С всеки от тях вече си работил в модулите „Класове и обекти“ и „Наследяване и полиморфизъм“:
| Принцип | Какво казва | Коя пукнатина оправя |
|---|---|---|
| Капсулация | Обектът пази данните си сам. Отвън се стига до тях само през методи, които проверяват. | Грешният запис: -10% се отказва. |
| Наследяване | Общото на няколко класа е написано веднъж, в родител. | Бонусът е само в Manager, а общото (име, заплата, проверката в повишението) е веднъж в Employee. |
| Полиморфизъм | Едно извикване, а всеки обект отговаря по своя начин. | If-ът по роля: всеки клас казва сам заплатата си, без второ копие. |
| Абстракция | Кодът зависи от това какво може обектът, а не как го прави. | Нов вид плащане влиза, без да пипаш превода. |
Накратко: капсулацията пази, наследяването споделя, полиморфизмът избира, абстракцията обещава.
Нататък всеки принцип оправя по една пукнатина в програмата за заплати. Накрая ще ги видиш заедно, в същата програма.
Идеята
Капсулация: обектът пази данните си
Заплатата е на служителя. Нека само той я сменя. Полето става private, а повишението е метод, който проверява:
class Employee { private String name; private int salary;
public Employee(String name, int salary) { this.name = name; this.salary = salary; }
public String getName() { return name; }
public int getSalary() { return salary; }
public void raise(int percent) { if (percent > 0) { salary += salary * percent / 100; } }}Employee ivan = new Employee("Иван", 2000);ivan.raise(-10);System.out.println(ivan.getSalary());ivan.raise(10);System.out.println(ivan.getSalary());20002200-10% е отказано, 10% минава. Проверката е на едно място, в класа, и всеки код, който иска да смени заплатата, минава през нея. Това е капсулация: данните и правилата за тях са заедно в обекта, а отвън се вижда само каквото класът реши да покаже.
Ред, който пише направо в заплатата, вече не се компилира:
ivan.salary = -2000;error: salary has private access in EmployeeЗаплатата се чете с getter getSalary(), а се сменя само с raise(). В Java getter-ите се пишат на ръка.
Кога вреди: getter и setter за всичко. Ако всяко поле има getter и setter, който записва каквото му дадеш, полето пак е отворено, само с повече редове. Капсулацията не е „скрито поле плюс get и set“. Тя е: обектът решава какво може да се промени и как. Затова Employee няма метод, който записва заплата, а има повишение с проверка. Започни със скрито и отваряй само каквото трябва.
По-точно, за любопитните
privateпази полето от кода извън класа, не от самия клас: всеки метод наEmployeeго пипа. Затова правилото пазят методите на класа.- Полето, което не се сменя след конструктора (името), може да е и
final. Тогава и самият клас не може да го пипне по-късно.
Пробвай
Кое скрива заплатата?
class Employee {
private String name;
int salary;private: полето е видимо само за кода в Employee. Отвън заплатата се сменя само с raise().Идеята
Наследяване: общото веднъж
Мениджърът е служител с бонус. Вместо копие на Employee с още едно поле, Manager наследява всичко и добавя само бонуса:
class Manager extends Employee { private int bonus;
public Manager(String name, int salary, int bonus) { super(name, salary); this.bonus = bonus; }
public int getBonus() { return bonus; }}Manager maria = new Manager("Мария", 3000, 500);maria.raise(-10);maria.raise(10);System.out.println(maria.getName() + ": " + maria.getSalary() + " + " + maria.getBonus());Мария: 3300 + 500Мениджърът няма своя проверка в повишението. Взима я от Employee: -10% е отказано, а 10% вдига 3000 на 3300. Ако утре правилото за повишенията се смени, сменяш го на едно място и то важи за всички наследници. Това е наследяване: общото е в родителя, а наследникът пише само своето.
Заплатата е private в Employee, затова и Manager не я вижда направо. Той дава името и заплатата на конструктора на родителя с super(name, salary), а после ги чете като всеки друг, през getSalary(). В Java и C# класът има точно един родител.
Проверката дали наследяването има смисъл е изречението „X е Y“. „Мениджърът е служител“ е вярно.
Кога вреди: дълги вериги и наследяване заради готов код. Employee → Manager → SeniorManager → RegionalSeniorManager: за да разбереш какво прави най-долният клас, четеш четири класа, а промяна в най-горния мърда всички под него. Друга честа грешка е да наследиш клас само за да вземеш готов метод: фактура, която наследява Employee, за да има име. „Фактурата е служител“ не е вярно, и тя ще получи заплата и повишение. Какво се прави вместо това, е в картата „Какво се бърка“ по-долу.
Идеята
Полиморфизъм: всеки клас казва сам
Сега пукнатината с копията. Вместо if по роля, всеки клас казва сам каква е месечната му заплата. Родителят има обща версия, а наследниците я презаписват:
class Employee { // ... name, salary, getSalary() и raise() отпреди
public int monthlyPay() { return salary; }}
class Manager extends Employee { // ... bonus и конструкторът
@Override public int monthlyPay() { return getSalary() + bonus; }}
class Intern extends Employee { // ... конструкторът
@Override public int monthlyPay() { return getSalary() / 2; }}И фишът, и преводът викат един и същ метод:
static void transfer(Employee[] staff) { int total = 0; for (Employee e : staff) { System.out.println("Превод: " + e.getName() + " " + e.monthlyPay() + " €"); total += e.monthlyPay(); } System.out.println("Общо: " + total + " €");}Employee[] staff = { new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Intern("Ана", 1600)};for (Employee e : staff) { System.out.println("Фиш: " + e.getName() + " " + e.monthlyPay() + " €");}transfer(staff);Фиш: Иван 2000 €Фиш: Мария 3500 €Фиш: Ана 800 €Превод: Иван 2000 €Превод: Мария 3500 €Превод: Ана 800 €Общо: 6300 €Ана получава 800 € и във фиша, и в превода. Правилото за стажанта е само в Intern и няма второ копие, което да се размине. Нов вид служител е нов клас със своя метод, а цикълът и преводът не се пипат. Това е полиморфизъм: едно извикване, e.monthlyPay(), а всеки обект отговаря по своя начин.
В Java методът се презаписва по подразбиране, без специална дума в родителя (освен ако е final или private). Коя версия се изпълнява, решава обектът, докато програмата работи, а не типът на променливата. @Override кара компилатора да провери, че наистина презаписваш метод на родителя, а не пишеш нов с грешно име.
Кога вреди. Два случая, които никога няма да станат три, не искат йерархия от класове: един if е по-кратък и по-ясен. И още: наследник, който презаписва метода, но нарушава обещанието на родителя (заплата, която излиза отрицателна, или метод, който спира програмата), чупи всеки цикъл, който вярва на родителя.
Пробвай
Колко ще преведе банката?
Класовете са от картите горе, заедно с договора за плащане от следващата карта: amountDue() е в Employee и връща monthlyPay().
Employee e = new Intern("Ана", 1600);e.raise(10);System.out.println(e.amountDue());Повишението е от Employee: 1600 + 10% = 1760. amountDue() също е от Employee, но вика monthlyPay() на обекта, а обектът е стажант. Изпълнява се версията на Intern: 1760 / 2 = 880. Трите принципа в три реда.
Идеята
Абстракция: какво, а не как
Счетоводството иска в същия превод и фактурите на доставчиците. Фактурата не е служител. Но преводът и не се интересува кой какъв е. Трябват му две неща: на кого и колко. Това е договорът:
interface Payable { int amountDue();
String payee();}
class Employee implements Payable { // ... всичко отпреди
@Override public int amountDue() { return monthlyPay(); }
@Override public String payee() { return name; }}
static void transfer(Payable[] payments) { int total = 0; for (Payable p : payments) { System.out.println("Превод: " + p.payee() + " " + p.amountDue() + " €"); total += p.amountDue(); } System.out.println("Общо: " + total + " €");}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; }}Payable[] payments = { new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Intern("Ана", 1600), new Invoice("Доставчик на ток", 340)};transfer(payments);Превод: Иван 2000 €Превод: Мария 3500 €Превод: Ана 800 €Превод: Доставчик на ток 340 €Общо: 6640 €transfer не знае нищо за заплати, бонуси и фактури. Знае само договора. Това е абстракция: показваш какво може обектът и криеш как го прави. Преводът зависи от абстракцията, затова всичко, което я изпълнява, влиза в него без промяна.
В Java договорът е interface. Employee и Invoice нямат общ родител освен Object, но и двата пишат implements Payable, и компилаторът проверява, че методите са написани. Manager и Intern получават договора от Employee.
Договорът е едната форма на абстракцията. Другата е абстрактният клас: Shape обещава лице, а всяка фигура казва как се смята („Абстрактни класове“). Кога кое, е в урока „Интерфейси“.
Кога вреди: абстракция твърде рано. Интерфейс с една-единствена реализация, „за всеки случай“, е още едно име, още един файл и още един скок, докато четеш. Абстракцията се изплаща, когато има поне две реализации или когато трябва да подмениш едната, например в тест. Payable се появи, когато дойде фактурата, не преди това.
Виж го
Четирите заедно
Сцената минава през програмата от началото: всеки принцип оправя своята пукнатина, а числата долу са от същата програма.
Employee[] staff = { new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Intern("Ана", 1600)};staff[0].raise(-10);for (Employee e : staff) { System.out.println("Фиш: " + e.getName() + " " + e.monthlyPay() + " €");}Payable[] payments = { staff[0], staff[1], staff[2], new Invoice("Доставчик на ток", 340) };transfer(payments);static int monthlyPay(Employee e) { if (e.role.equals("мениджър")) { return e.salary + e.bonus; } else if (e.role.equals("стажант")) { return e.salary / 2; } return e.salary;}
static void transfer(Employee[] staff) { int total = 0; for (Employee e : staff) { int amount = e.salary; if (e.role.equals("мениджър")) { amount += e.bonus; } System.out.println("Превод: " + e.name + " " + amount + " €"); total += amount; } System.out.println("Общо: " + total + " €");}
int percent = -10;staff[0].salary += staff[0].salary * percent / 100;class Employee { private String name; private int salary;
public Employee(String name, int salary) { this.name = name; this.salary = salary; }
public String getName() { return name; }
public int getSalary() { return salary; }
public void raise(int percent) { if (percent > 0) { salary += salary * percent / 100; } }}class Manager extends Employee { private int bonus;
public Manager(String name, int salary, int bonus) { super(name, salary); this.bonus = bonus; }
public int getBonus() { return bonus; }}class Employee { // ... name, salary, getSalary() и raise() отпреди
public int monthlyPay() { return salary; }}
class Manager extends Employee { // ... bonus и конструкторът
@Override public int monthlyPay() { return getSalary() + bonus; }}
class Intern extends Employee { // ... конструкторът
@Override public int monthlyPay() { return getSalary() / 2; }}interface Payable { int amountDue();
String payee();}
class Employee implements Payable { // ... всичко отпреди
@Override public int amountDue() { return monthlyPay(); }
@Override public String payee() { return name; }}
static void transfer(Payable[] payments) { int total = 0; for (Payable p : payments) { System.out.println("Превод: " + p.payee() + " " + p.amountDue() + " €"); total += p.amountDue(); } System.out.println("Общо: " + total + " €");}Employee[] staff = { new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Intern("Ана", 1600)};staff[0].raise(-10);for (Employee e : staff) { System.out.println("Фиш: " + e.getName() + " " + e.monthlyPay() + " €");}Payable[] payments = { staff[0], staff[1], staff[2], new Invoice("Доставчик на ток", 340) };transfer(payments);Стъпка по стъпка, с думи
- Преди: ролята е текст, правилото е копирано в две вериги от if, а заплатата я пише всеки. Иван е на 1800 €, а фишът и преводът на Ана се разминават.
- Капсулация: заплатата е скрита, а raise() отказва -10%. Иван остава на 2000 €.
- Наследяване: Manager взима името, заплатата и проверката в повишението от Employee и добавя само бонуса. Обектите вече имат класове, а не роля в текст.
- Полиморфизъм: всеки клас казва сам своя monthlyPay(). Преводът вика същия метод като фиша и Ана получава 800 € и в двата.
- Абстракция: преводът знае само интерфейса Payable. Фактурата за ток влиза, без да е служител.
- Четирите заедно: същите -10% са отказани, всеки фиш съвпада с превода си, а общо е 6640 €.
Ето програмата преди и след, със 10% за Иван. Процедурата „преди“ е поправена на ръка: правилото за стажанта е добавено и в превода.
static int monthlyPay(Employee e) { if (e.role.equals("мениджър")) { return e.salary + e.bonus; } else if (e.role.equals("стажант")) { return e.salary / 2; } return e.salary;}
static void transfer(Employee[] staff) { int total = 0; for (Employee e : staff) { int amount = e.salary; if (e.role.equals("мениджър")) { amount += e.bonus; } else if (e.role.equals("стажант")) { amount = e.salary / 2; } System.out.println("Превод: " + e.name + " " + amount + " €"); total += amount; } System.out.println("Общо: " + total + " €");}Employee[] staff = { new Employee("Иван", "служител", 2000, 0), new Employee("Мария", "мениджър", 3000, 500), new Employee("Ана", "стажант", 1600, 0)};int percent = 10;staff[0].salary += staff[0].salary * percent / 100;for (Employee e : staff) { System.out.println("Фиш: " + e.name + " " + monthlyPay(e) + " €");}transfer(staff);Фиш: Иван 2200 €Фиш: Мария 3500 €Фиш: Ана 800 €Превод: Иван 2200 €Превод: Мария 3500 €Превод: Ана 800 €Общо: 6500 €static void transfer(Payable[] payments) { int total = 0; for (Payable p : payments) { System.out.println("Превод: " + p.payee() + " " + p.amountDue() + " €"); total += p.amountDue(); } System.out.println("Общо: " + total + " €");}Employee[] staff = { new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Intern("Ана", 1600)};staff[0].raise(10);for (Employee e : staff) { System.out.println("Фиш: " + e.getName() + " " + e.monthlyPay() + " €");}transfer(staff);Фиш: Иван 2200 €Фиш: Мария 3500 €Фиш: Ана 800 €Превод: Иван 2200 €Превод: Мария 3500 €Превод: Ана 800 €Общо: 6500 €Изходът е същият. Разликата е в това, което ще стане утре:
- Някой въведе -10 →
raise()го отказва (капсулация). - Ново правило за повишенията → сменя се на едно място, в
Employee(наследяване). - Нов вид служител → нов клас със своя
monthlyPay(); фишът и преводът не се пипат (полиморфизъм). - Ново нещо за плащане → нов клас, който изпълнява договора; преводът не се пипа (абстракция).
Същата програма с грешните -10% от проблема и с фактурата:
Employee[] staff = { new Employee("Иван", 2000), new Manager("Мария", 3000, 500), new Intern("Ана", 1600)};staff[0].raise(-10);for (Employee e : staff) { System.out.println("Фиш: " + e.getName() + " " + e.monthlyPay() + " €");}Payable[] payments = { staff[0], staff[1], staff[2], new Invoice("Доставчик на ток", 340) };transfer(payments);Фиш: Иван 2000 €Фиш: Мария 3500 €Фиш: Ана 800 €Превод: Иван 2000 €Превод: Мария 3500 €Превод: Ана 800 €Превод: Доставчик на ток 340 €Общо: 6640 €Всички класове на едно място
interface Payable { int amountDue();
String payee();}
class Employee implements Payable { private String name; private int salary;
public Employee(String name, int salary) { this.name = name; this.salary = salary; }
public String getName() { return name; }
public int getSalary() { return salary; }
public void raise(int percent) { if (percent > 0) { salary += salary * percent / 100; } }
public int monthlyPay() { return salary; }
@Override public int amountDue() { return monthlyPay(); }
@Override public String payee() { return name; }}
class Manager extends Employee { private int bonus;
public Manager(String name, int salary, int bonus) { super(name, salary); this.bonus = bonus; }
public int getBonus() { return bonus; }
@Override public int monthlyPay() { return getSalary() + bonus; }}
class Intern extends Employee { public Intern(String name, int salary) { super(name, salary); }
@Override public int monthlyPay() { return getSalary() / 2; }}
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; }}Пробвай
Подреди мениджъра
Редовете на Manager са разбъркани. Подреди ги в обичайния ред: полето, конструкторът, после методът за заплатата.
class Manager extends Employee {public Manager(String name, int salary, int bonus) {}@Overridepublic int monthlyPay() {private int bonus;this.bonus = bonus;}}return getSalary() + bonus;super(name, salary);
Пробвай
Намери грешката
Колега „опрости“ договора в Employee. Програмата е като в „Четирите заедно“, а печата:
Фиш: Иван 2200 €Фиш: Мария 3500 €Фиш: Ана 800 €Превод: Иван 2200 €Превод: Мария 3000 €Превод: Ана 1600 €Общо: 6800 €Фишът на Мария е 3500 €, а банката ѝ превежда 3000 €. На кой ред е грешката?
Избери реда с грешката.
amountDue() връща заплатата направо, вместо да пита monthlyPay(). Това е пак второто копие на правилото от проблема, само по-кратко: бонусът на мениджъра и половината на стажанта са изпуснати. Методът на родителя, който вика презаписан метод, е мястото, където полиморфизмът работи за теб. Поправката: return monthlyPay();.
Идеята
Какво се бърка
Абстракция или капсулация? И двете крият нещо, но различно. Капсулацията крие данните на един обект, за да не ги развали никой отвън: заплатата е скрита, повишението проверява. Абстракцията крие разликите между много класове зад общо обещание: преводът вижда само amountDue() и payee(). Капсулацията гледа обекта отвътре, абстракцията гледа кода, който го ползва отвън.
Наследяване или композиция? Наследяването казва „е“: мениджърът е служител. Композицията казва „има“: служителят има банкова сметка. Ако направиш служителя наследник на сметката от урока „Капсулация“, за да вземеш deposit() наготово,
class Employee extends Account { // ... name и salary}той ще получи и теглене, и баланс, и всичко, което сметката научи утре. А „служителят е сметка“ не е вярно. Вместо това сметката е поле на служителя:
class Employee { private String name; private int salary; private Account account;
public Employee(String name, int salary, Account account) { this.name = name; this.salary = salary; this.account = account; }
public void payday() { account.deposit(salary); }}Account account = new Account(100);Employee ivan = new Employee("Иван", 2000, account);ivan.payday();System.out.println(account.getBalance());2100Затова честият съвет е „предпочитай композиция пред наследяване“. Наследявай само когато „X е Y“ е вярно и ще остане вярно. Иначе обектът да има другия като поле.
Претоварване или презаписване? И двете понякога се наричат полиморфизъм. Претоварването е няколко метода с едно име и различни параметри: компилаторът избира по аргументите, преди програмата да тръгне. Презаписването е същият метод в наследника: изборът е по обекта, докато програмата работи. Когато се казва „полиморфизъм“ в ООП, почти винаги е второто.
По-точно, за любопитните
- Някои книги изброяват три стълба: капсулация, наследяване и полиморфизъм. Абстракцията тогава е общата идея под тях. На интервю по-често се очакват четирите.
- Принципите не са правила, които се спазват „по 100%“. Всеки има цена: скрити полета значат повече методи, йерархиите значат повече класове, договорите значат още едно име. Слагаш ги там, където пукнатината е истинска.
- След тези четири идват петте принципа SOLID за код, който понася промени, и шаблоните за дизайн: готови решения, построени от същите четири. Те са в курса „Софтуерно инженерство“: SOLID и Шаблони за дизайн.
Упражнение
Кой принцип е нарушен?
Класически въпрос на интервю. За всеки фрагмент отговори на глас: кой принцип е нарушен, какво ще се счупи и как го оправяш.
1.
class Cart { public double total;}
// ... на 12 места в програмата:cart.total -= discount;2.
static double area(Shape s) { if (s instanceof Circle c) return 3.14 * c.r * c.r; if (s instanceof Rectangle r) return r.w * r.h; return 0;}3.
class Invoice extends Employee { // взима name и payee() наготово}4.
interface SalaryProvider { int provide(Employee e);}
// единствената реализация:class DefaultSalaryProvider implements SalaryProvider { public int provide(Employee e) { return e.monthlyPay(); }}Отговорите
- Капсулация. Сумата е отворена и се намалява от 12 места. Отстъпка, по-голяма от сумата, я прави отрицателна, и никой не пита. Сумата става private, а отстъпката е метод на количката, който проверява.
- Полиморфизъм (и абстракция). If по вид на фигурата: нова фигура значи нов ред тук и навсякъде, където някой пита за вида. Лицето става абстрактен метод на
Shape, а всяка фигура го пише сама. - Наследяване на грешното място. „Фактурата е служител“ не е вярно: тя получава заплата, повишение и месечно плащане. Общото им е, че се плащат: договор
Payable. - Абстракция твърде рано. Договор с една реализация, която само препраща към
monthlyPay(), е още едно име и още един скок при четене, без да пази от нищо. Махни го и викай метода направо. Договорът ще има смисъл, когато се появи втора реализация.
Упражнение
Хонорар и тераса
Реши задачите в редактора си.
1. Изпълнител на хонорар. Contractor (Петър, 40 часа по 25 €) не е служител: няма фиш, но трябва да е в превода. Добави го така, че transfer да не се промени нито с един ред. В превода сложи и фактура за наем, 1800 € за наемодателя.
Превод: Иван 2000 €Превод: Мария 3500 €Превод: Ана 800 €Превод: Петър 1000 €Превод: Наемодател 1800 €Общо: 9100 €2. Терасата без if. Тази процедура смята лице по вид на фигурата:
static int area(String kind, int a, int b) { if (kind.equals("правоъгълник")) return a * b; if (kind.equals("триъгълник")) return a * b / 2; if (kind.equals("квадрат")) return a * a; return 0;}Напиши я с абстрактен Shape и класове Rectangle, Triangle и Square. Терасата е правоъгълник 4 × 3, триъгълник с основа 2 и височина 3 и квадрат със страна 2. Квадратът е правоъгълник с равни страни: напиши го като наследник на Rectangle, без своя формула за лицето.
Правоъгълник: 12 м²Триъгълник: 3 м²Квадрат: 4 м²Общо: 19 м²Още задачи
- Хонорар и във фиша. Ако изпълнителят все пак иска фиш, без да става служител: какво трябва да има в договора и кой го изпълнява?
- Заплата по сметка. Дай на служителя сметка (композиция, както в „Какво се бърка“) и метод, който в деня на заплатата внася
monthlyPay(), а не заплатата. Мениджърът трябва да получи и бонуса, без да пипашmonthlyPay()наManager. - Повишение за всички. Вдигни заплатите на всички служители с 5% в един цикъл. Кои класове пипаш?
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Полиморфизъм · Абстрактни класове · Интерфейси · Проверка на типа
- Задачи по темата: Наследяване и полиморфизъм