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

Капсулация

Проблем

Минус 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 Account

private и public се казват модификатори за достъп. Те казват кой може да вижда поле, метод или конструктор. Класът крие данните си и ги показва само през свои методи. Това се казва капсулация.

По-точно, за любопитните
  • Без модификатор полето или методът се виждат в същия пакет (група класове в една папка). Докато всичко е в един файл, това е почти като public. Затова пиши модификатора изрично.
  • Има и protected: видимо в класа и в наследниците му. Ще го срещнем в модул „Наследяване и полиморфизъм“.
  • public class Main е публичен клас. Във файла може да има само един такъв и той се казва като файла. Затова нашите класове над него са без public.

Идеята

Да четеш, без да пипаш

Отвън вече не можем и да прочетем баланса. Затова класът дава метод, който само го връща:

Main.java
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());
Стъпка по стъпка, с думи
  1. balance е private: стъклото значи, че отвън до полето няма път.
  2. Тегленето минава през проверката в метода: 500 е повече от 100. Отказано, балансът не се пипа.
  3. Внасянето също се проверява: 50 е положителна сума. Методът я добавя.
  4. getBalance() дава навън копие на баланса. Самото поле остава скрито.

Пробвай

Отказано

Какво ще отпечата програмата?

Account account = new Account("Мария", 100);
System.out.println(account.withdraw(150));
account.deposit(-20);
System.out.println(account.getBalance());
Отговор

Идеята

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());
Изход
4

setAge е 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, а балансът пак става -500:

Account account = new Account();
account.setBalance(100);
account.withdraw(30);
account.setBalance(-500);
System.out.println(account.getBalance());
Изход
-500.0

Кой ред в класа пробива защитата?

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

Упражнение

Скрити данни

Реши задачите в редактора си. Всички полета са private.

1. Човек. Person с име и възраст, конструктор, getter-и getName() и getAge() и introduce().

Изход
Мария е на 34 г.
Здравей, аз съм Мария и съм на 34 г.

2. Сметка без минус. Account със собственик и баланс. Внася се само положителна сума. Тегленето връща дали е минало и не пуска баланса под 0. Балансът се чете, но не се записва отвън.

Main
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() + " €");
Изход
false
true
Баланс: 35.0 €

3. Животно. Animal с вид и възраст. Видът се задава в конструктора и само се чете. Възрастта има setter, който отказва отрицателно число и печата съобщение. Конструкторът също минава през тази проверка.

Main
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.

Тест

Провери се

1. Полето balance е private. Откъде може да се променя?
2. Защо сметката няма setter за баланса?
3. setAge(-3) се вика, а setAge проверява age >= 0. Какво става с възрастта?
4. Какво значи капсулация?

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