Капсулация
Проблем
Минус 500 €
В миналия урок сложихме проверката за теглене в метод. Само че полето е отворено и всеки може да пише в него направо:
Account account = new Account("Мария", 100);account.withdraw(500);account.balance = -500;System.out.println(account.balance);-500.0Методът правилно отказа да изтегли 500 €. Следващият ред просто записа баланса на ръка. Проверката е там, но може да се заобиколи. Трябва начин да кажем „балансът се пипа само от сметката“.
Идеята
private: само класът пипа
Сложи пред полето private вместо public:
class Account { private String owner; private double balance; // ...}Сега до полето стига само кодът вътре в класа Account. Отвън редът
account.balance = -500;вече не се компилира:
error: balance has private access in Accountprivate и public се казват модификатори за достъп. Те казват кой може да вижда поле, метод или конструктор. Класът крие данните си и ги показва само през свои методи. Това се казва капсулация.
По-точно, за любопитните
- Без модификатор полето или методът се виждат в същия пакет (група класове в една папка). Докато всичко е в един файл, това е почти като
public. Затова пиши модификатора изрично. - Има и
protected: видимо в класа и в наследниците му. Ще го срещнем в модул „Наследяване и полиморфизъм“. public class Mainе публичен клас. Във файла може да има само един такъв и той се казва като файла. Затова нашите класове над него са безpublic.
Идеята
Да четеш, без да пипаш
Отвън вече не можем и да прочетем баланса. Затова класът дава метод, който само го връща:
class Account { private String owner; private double balance;
public Account(String owner, double balance) { this.owner = owner; this.balance = balance; }
public String getOwner() { return owner; }
public double getBalance() { return balance; }
public void deposit(double amount) { if (amount > 0) { balance += amount; } }
public boolean withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; return true; } return false; }}
public class Main { public static void main(String[] args) { Account account = new Account("Мария", 100); account.withdraw(30); account.deposit(50); System.out.println(account.getOwner() + ": " + account.getBalance() + " €"); }}Мария: 120.0 €getBalance() е getter: метод, който връща стойността на поле. Името е get и името на полето с главна буква. Сменя се балансът само през deposit и withdraw, а те проверяват.
withdraw вече връща boolean: дали тегленето е минало. Така който тегли, разбира дали е успял.
Account account = new Account("Мария", 100);account.withdraw(500);account.deposit(50);System.out.println(account.getBalance());Стъпка по стъпка, с думи
- balance е private: стъклото значи, че отвън до полето няма път.
- Тегленето минава през проверката в метода: 500 е повече от 100. Отказано, балансът не се пипа.
- Внасянето също се проверява: 50 е положителна сума. Методът я добавя.
- getBalance() дава навън копие на баланса. Самото поле остава скрито.
Пробвай
Отказано
Какво ще отпечата програмата?
Account account = new Account("Мария", 100);System.out.println(account.withdraw(150));account.deposit(-20);System.out.println(account.getBalance());150 € е повече от баланса, затова withdraw връща false и не пипа баланса. Внасянето на -20 € също се отказва, защото сумата трябва да е положителна. Балансът остава 100.
Идеята
Setter: промяна с проверка
Понякога полето трябва да може да се сменя отвън, но не с каквато и да е стойност. Възрастта на животно може да се промени, но не може да е отрицателна:
class Animal { private int age;
public int getAge() { return age; }
public void setAge(int age) { if (age >= 0) { this.age = age; } }}
Animal cat = new Animal();cat.setAge(4);cat.setAge(-3);System.out.println(cat.getAge());4setAge е setter: метод, който записва стойност в поле, след като я провери. При -3 проверката не минава и възрастта остава 4.
Идеята
Кое поле да има setter?
Не всяко поле има нужда от setter, а някои нямат нужда и от getter. Помисли какво трябва да може да се прави с продукта отвън:
- името и цената се задават при създаването и после само се четат: само getter;
- бройката в количката се сменя, но поне 1: getter и setter с проверка.
class Product { private String name; private double price; private int quantity;
public Product(String name, double price) { this.name = name; this.price = price; this.quantity = 1; }
public String getName() { return name; }
public double getPrice() { return price; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { if (quantity >= 1) { this.quantity = quantity; } }}
Product coffee = new Product("Кафе", 7.50);coffee.setQuantity(3);coffee.setQuantity(0);System.out.println(coffee.getName() + " x" + coffee.getQuantity());Кафе x3Правилото е: започни с private за всичко и отваряй само каквото трябва. Setter, който записва всичко без проверка, връща проблема от първата карта.
В Java getter-ите и setter-ите се пишат на ръка. IntelliJ IDEA ги генерира: Alt+Insert → Getter and Setter.
Пробвай
Кое липсва?
class Account {
double balance;
}private: полето е видимо само за кода в Account.Пробвай
Намери грешката
Полето е private, а балансът пак става -500:
Account account = new Account();account.setBalance(100);account.withdraw(30);account.setBalance(-500);System.out.println(account.getBalance());-500.0Кой ред в класа пробива защитата?
Избери реда с грешката.
setBalance записва всяка стойност без проверка. Полето е скрито, но setter-ът го отваря отново. Махни го и задавай началния баланс в конструктора. Сменя се само с deposit и withdraw.
Упражнение
Скрити данни
Реши задачите в редактора си. Всички полета са private.
1. Човек. Person с име и възраст, конструктор, getter-и getName() и getAge() и introduce().
Мария е на 34 г.Здравей, аз съм Мария и съм на 34 г.2. Сметка без минус. Account със собственик и баланс. Внася се само положителна сума. Тегленето връща дали е минало и не пуска баланса под 0. Балансът се чете, но не се записва отвън.
Account account = new Account("Петър", 50);account.deposit(25);account.deposit(-10);System.out.println(account.withdraw(100));System.out.println(account.withdraw(40));System.out.println("Баланс: " + account.getBalance() + " €");falsetrueБаланс: 35.0 €3. Животно. Animal с вид и възраст. Видът се задава в конструктора и само се чете. Възрастта има setter, който отказва отрицателно число и печата съобщение. Конструкторът също минава през тази проверка.
Animal cat = new Animal("котка", 3);cat.setAge(4);cat.setAge(-1);System.out.println(cat.getSpecies() + ", " + cat.getAge() + " г.");Невалидна възраст: -1котка, 4 г.Още задачи
- Оценка. Клас
Gradeс име на предмет и оценка от 2 до 6. Setter-ът отказва всичко извън тези граници. Пробвай с 5, 7 и 1. - Продукт с отстъпка. Към
Productот урока добави метод за отстъпка в проценти, който приема само от 1 до 90%. Цената няма setter.
Тест