Капсулация и модификатори за достъп

Капсулацията е принцип в обектно-ориентираното програмиране, при който данните на обекта и операциите върху тези данни се поставят в един клас. Външният код не работи директно с вътрешното състояние на обекта, а използва методите, които класът предоставя.

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

class Student {

    String name;
    int age;
}

При такава декларация полетата могат да бъдат променяни директно.

Student student = new Student();
student.age = -5;

Стойността -5 не е валидна възраст, но директният достъп до полето позволява такава промяна. Капсулацията решава този проблем чрез ограничаване на достъпа до полетата.

Частни полета

Поле, декларирано с private, е достъпно само в класа, в който е дефинирано.

class Student {

    private String name;
    private int age;
}

След тази промяна полетата name и age не могат да бъдат достъпени директно извън класа Student.

Student student = new Student();

// student.age = -5; // не се компилира

Обектът все още има полетата name и age, но достъпът до тях трябва да се реализира чрез методи.

Методи за четене

Метод за четене връща стойността на поле. Такъв метод често се нарича getter.

class Student {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Методите getName() и getAge() са публични. Те позволяват стойностите да бъдат прочетени, без полетата да стават директно достъпни.

Методи за промяна

Метод за промяна задава нова стойност на поле. Такъв метод често се нарича setter.

class Student {

    private String name;
    private int age;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        }
    }
}

Методът setAge(int age) проверява стойността преди присвояване. Ако подадената стойност е отрицателна, полето не се променя. Така класът контролира собственото си състояние.

Капсулация чрез конструктор

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

class Student {

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        setAge(age);
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        }
    }
}

В конструктора се използва setAge(age), защото проверката за валидна възраст вече е описана в метода setAge. Така условието за валидност се намира на едно място.

Обект само за четене

Понякога стойностите трябва да се зададат при създаване на обекта и след това да не се променят. В такъв случай се дефинират частни полета и методи за четене, но не се дефинират методи за промяна.

class StudentCard {

    private final String number;
    private final String ownerName;

    public StudentCard(String number, String ownerName) {
        this.number = number;
        this.ownerName = ownerName;
    }

    public String getNumber() {
        return number;
    }

    public String getOwnerName() {
        return ownerName;
    }
}

Полетата са private final. Те се задават в конструктора и след това не могат да получат нова стойност.

final поле

Поле, декларирано с final, може да получи стойност само веднъж. След това към него не може да бъде присвоена нова стойност.

class Student {

    private final String facultyNumber;

    public Student(String facultyNumber) {
        this.facultyNumber = facultyNumber;
    }

    public String getFacultyNumber() {
        return facultyNumber;
    }
}

final поле трябва да бъде инициализирано при декларацията или в конструктор. Това позволява създаване на обекти, при които определени части от състоянието не могат да се променят след създаване.

Публичен интерфейс на клас

Публичният интерфейс на клас се състои от публичните конструктори и публичните методи, които могат да се използват от външен код. Частните полета и частните помощни методи са част от вътрешната реализация.

class BankAccount {

    private double balance;

    public BankAccount(double initialBalance) {
        if (initialBalance >= 0) {
            balance = initialBalance;
        }
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
}

Класът BankAccount не позволява директна промяна на balance. Промяната се извършва чрез метод deposit(double amount), който приема само положителна стойност.

Модификатори за достъп

Модификаторите за достъп определят откъде може да се използва даден клас, поле, конструктор или метод. Те са механизъм за контрол на видимостта в Java и са основен инструмент за реализиране на капсулация.

В Java има четири нива на достъп за членове на клас:

  • private;
  • достъп на ниво пакет, когато не е указан модификатор;
  • protected;
  • public.

private

Модификаторът private ограничава достъпа само до класа, в който е деклариран съответният член.

class Student {

    private int age;

    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        }
    }
}

Полето age може да се използва директно само в класа Student. Външен код трябва да използва публичен метод, ако класът предоставя такъв.

private се използва за полета, които описват вътрешното състояние на обекта, и за помощни методи, които не трябва да бъдат част от публичния интерфейс.

Достъп на ниво пакет

Когато не е указан модификатор, се използва достъп на ниво пакет. Такъв член е достъпен само за класове от същия пакет.

class Student {

    String name;
}

Полето name няма изрично указан модификатор. То е достъпно за класове от същия пакет, но не е достъпно от класове в други пакети.

Пакетът представлява група от свързани класове. В начални примери често не се декларира пакет, но правилото за достъп на ниво пакет остава същото.

protected

Модификаторът protected позволява достъп от класове в същия пакет и от класове наследници.

class Person {

    protected String name;
}

class Student extends Person {

    public String getName() {
        return name;
    }
}

Класът Student наследява Person и може да използва полето name, защото то е protected.

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

public

Модификаторът public прави елемента достъпен от всяка част на програмата, ако самият клас е достъпен.

public class Student {

    public String getInformation() {
        return "Student";
    }
}

Публичните методи формират начина, по който външният код работи с обекта. При капсулация полетата обикновено не се декларират като public, защото това позволява директна промяна без контрол.

Модификатори при класове

Клас на най-горно ниво може да бъде public или без изричен модификатор.

public class Student {

}

Публичен клас е достъпен от други пакети. Ако класът няма модификатор, той е достъпен само в рамките на същия пакет.

Таблица за видимост

Модификатор Същият клас Същият пакет Наследник в друг пакет Външен пакет
private Да Не Не Не
без модификатор Да Да Не Не
protected Да Да Да Не
public Да Да Да Да

При избор на модификатор се използва най-ограниченият достъп, който позволява класът да изпълнява предназначението си. Полетата се ограничават с private, когато няма причина да бъдат достъпни директно отвън.


This site uses Just the Docs, a documentation theme for Jekyll.