Liigu peamise sisu juurde

Kapseldamine

Sissejuhatus

Kapseldamine (encapsulation) on üks neljast objektorienteeritud programmeerimise põhiprintsiipidest. Kapseldamise põhiidee seisneb selles, et andmed ja meetodid, mis nende andmete peal töötavad, kirjutatakse kokku üheks klassiks ning läbi meetodite kontrollitakse, mida on väljaspoolt võimalik selle klassiga teha.

Praktikas tähendab kapseldamine kahte asja:

  • Sisemiste andmete peitmine private väljade näol.
  • public meetodite kaudu nendele andmetele kontrollitud ligipääsu andmine.

Probleem ilma kapseldamiseta

Olgu meil klass BankAccount, kus väljad on avalikult ligipääsetavad:

public class BankAccount {
public String owner;
public double balance;

// ...
}

Mitte miski ei takista antud klassi andmeid muutmast: võimalik on väljadele omistada ebasobivaid väärtusi, näiteks negatiivne saldo või null konto omanikuna. Antud koodile pääseb igalt poolt ligi ning selle klassi sisemist seisu on võimalik igalt poolt muuta, näiteks:

BankAccount account = new BankAccount();
account.owner = "Alice";
account.balance = 1000.0;

// Nothing stops this
account.balance = -99999;
account.owner = "";

Mida suuremaks projekt muutub, seda suuremaks probleemiks see ka saab:

  • Ootamatud muudatused objekti olekus võivad tekitada vigu, mida on raske leida.
  • Muudatused klassi struktuuris (nt: välja ümbernimetamine) võivad muu koodi katki teha.
  • Ei ole võimalik ühtselt tagada väärtuste valideerimist.

Klassi kapseldamine

Probleemi lahenduseks oleks klass ära kapseldada. Seda saavutatakse tehes kõik väljad private-iks ning luues nendele ligipääsemiseks vastavad meetodid:

public class BankAccount {

// private fields
private String owner;
private double balance;

// Constructor that does some logic checks during object creation time
public BankAccount(String owner, double initialBalance) {
if (owner == null || owner.isEmpty()) {
throw new IllegalArgumentException("Owner name cannot be empty");
}
if (initialBalance < 0) {
throw new IllegalArgumentException("Initial balance cannot be negative");
}
this.owner = owner;
this.balance = initialBalance;
}

// Getter methods to get data from class
public double getBalance() {
return balance;
}

public String getOwner() {
return owner;
}

// Setter methods to update the data with logic validation.
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive");
}
balance += amount;
}

public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Withdrawal amount must be positive");
}
if (amount > balance) {
throw new IllegalArgumentException("Insufficient funds");
}
balance -= amount;
}
}

Nüüd klass vastutab ja omab kontrolli oma oleku üle:

  • Saldot on võimalik muuta ainult läbi selleks ette nähtud meetodite (deposit(), withdraw()). Mõlema puhul kontrollitakse ka sisendit, et vastaks ette seatud reeglitele.
  • Väärolekute tekkimine on raskendatud nii objekti loomise hetkel kui ka oleku muutmise ajal.
  • Mujal koodist on võimalik ainult kasutada neid meetodeid, milleks on luba antud.

Näiteks:

BankAccount account = new BankAccount("Alice", 1000.0);

account.deposit(500.0);
System.out.println(account.getBalance()); // 1500.0

account.withdraw(200.0);
System.out.println(account.getBalance()); // 1300.0

account.balance = -99999; // Error: 'balance' has private access
account.withdraw(-100); // IllegalArgumentException

Getter ja setter meetodid

Selgitus

Privaatsetele väljadele ligipääsu tekitamiseks luuakse tavaliselt getter ja setter meetodid.

Getter meetod tagastab antud välja väärtuse, näiteks:

public double getBalance() {
return balance;
}

Setter meetod võimaldab väljale uut väärtust omistada, tavaliselt koos valideerimisega, näiteks:

public void setOwner(String owner) {
if (owner == null || owner.isEmpty()) {
throw new IllegalArgumentException("Owner name cannot be empty");
}
this.owner = owner;
}

Nimereeglid

Nimereeglid nende meetodite puhul on järgnevad:

  • Getter meetodi puhul pannakse meetodi nimeks: get + välja nimi suure algutähega, näiteks getBalance(), getOwner().
  • Setter meetodi puhul pannakse meetodi nimeks: set + välja nimi suure algustähega, näiteks setBalance(), setOwner().
    • Kui antud klassi kontekstis on muu nimi loogilisem (eelneva näite puhul withdraw(), deposit()), siis on ka see lubatud.
  • Tõeväärtustüüpi väljade puhul kirjutatakse get asemel is, näiteks isActive() või isLoggedIn().
class User {
private String username;
private boolean active;

public User(String username) {
this.username = username;
this.active = true;
}

public String getUsername() { return username; }

public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
}

Kõikjale pole vaja

Neid meetodeid ei pea igale väljale looma. See vajadus peaks tekkima klassi disainist ning kasutusjuhtudest. Setter-i loomine tekitab viisi, kuidas muu kood saab objekti seisu muuta. Kui antud välja ei ole vaja peale objekti loomist muuta, siis pole vaja ka sellele setter-it luua. Sama reegel kehtib ka getter-ite jaoks: kui seda pole vajadust väljaspool klassi kasutada, siis pole vaja seda ka tekitada.

class Invoice {
private long internalId; // internal IDs should never be exposed to the public
private final String invoiceNumber; // should never change - no setter
private double amount; // can be adjusted - has a setter

public Invoice(long internalId, String invoiceNumber, double amount) {
this.internalId = internalId;
this.invoiceNumber = invoiceNumber;
this.amount = amount;
}

public String getInvoiceNumber() { return invoiceNumber; }

public double getAmount() { return amount; }
public void setAmount(double amount) {
if (amount < 0) throw new IllegalArgumentException("Amount cannot be negative");
this.amount = amount;
}
}

Hästi kapseldatud klass vähendab sõltuvusi teiste koodiosadega (madalam sidusus) ning võimaldab peita teostusdetaile, pakkudes väljapoole ainult selgelt defineeritud liidest. See toetab ka abstraktsiooni põhimõtet – kasutaja ei pea teadma, kuidas klass oma tööd teeb, vaid ainult seda, mida ta teha võimaldab.