OBJEKTINIS PROGRAMAVIMAS

Java inkapsuliacija

Inkapsuliacija paslepia objekto vidinę būseną ir leidžia ją naudoti tik per aiškiai apibrėžtą viešą sąsają. Objektas pats kontroliuoja, kokie jo duomenų pakeitimai yra leidžiami.

Kodėl reikalinga inkapsuliacija?

  • apsaugo objektą nuo netinkamos būsenos;
  • paslepia vidines realizacijos detales;
  • suteikia vieną vietą duomenims tikrinti;
  • leidžia pakeisti vidinę realizaciją nekeičiant naudotojų kodo;
  • aiškiai parodo, kokius veiksmus klasė leidžia atlikti.

Viešo lauko problema

Viešą atributą bet kuris kodas gali pakeisti be klasės kontrolės:

public class Knyga {
    public int puslapiuSkaicius = 96;

    public static void main(String[] args) {
        Knyga knyga = new Knyga();
        knyga.puslapiuSkaicius = -5;

        System.out.println(knyga.puslapiuSkaicius);
    }
}
Konsolės rezultatas

-5

  1. Atributas puslapiuSkaicius yra public, todėl bet kuris išorinis kodas gali jį pakeisti.
  2. Pradinė tinkama reikšmė yra 96.
  3. Išorinis kodas tiesiogiai įrašo -5, nors knyga negali turėti neigiamo puslapių skaičiaus.
  4. Klasė neturi galimybės patikrinti ar atmesti šio pakeitimo.
  5. Objektas lieka nelogiškos būsenos, o konsolėje matome -5.

Privati būsena ir kontroliuojama prieiga

Laukas slepiamas su private, o vieši metodai nustato skaitymo ir keitimo taisykles:

public class Knyga {
    private int puslapiuSkaicius = 96;

    public int getPuslapiuSkaicius() {
        return puslapiuSkaicius;
    }

    public void setPuslapiuSkaicius(int naujasSkaicius) {
        if (naujasSkaicius <= 0) {
            System.out.println("Puslapių skaičius turi būti teigiamas");
            return;
        }

        this.puslapiuSkaicius = naujasSkaicius;
    }

    public static void main(String[] args) {
        Knyga knyga = new Knyga();
        knyga.setPuslapiuSkaicius(-5);

        System.out.println(knyga.getPuslapiuSkaicius());
    }
}
Konsolės rezultatas

Puslapių skaičius turi būti teigiamas
96

  1. Dabar atributas yra private, todėl išorinis kodas negali jo pakeisti tiesiogiai.
  2. Reikšmė keičiama tik per setterį setPuslapiuSkaicius().
  3. Setteris pirmiausia patikrina, ar perduotas skaičius didesnis už nulį.
  4. Reikšmė -5 netinkama, todėl parodomas pranešimas ir metodas baigiamas su return.
  5. Objekto būsena nepasikeičia, tad getteris vis dar grąžina tinkamą reikšmę 96.

Inkapsuliacija nėra vien getteriai ir setteriai

Kartais geriau atverti prasmingą objekto veiksmą, o ne bendrą setterį. Likusių knygos puslapių skaičių saugiau mažinti metodu, kuris patikrina skaitymo veiksmą:

public class Knyga {
    private int likoPuslapiu = 96;

    public int getLikoPuslapiu() {
        return likoPuslapiu;
    }

    public boolean skaityti(int puslapiai) {
        if (puslapiai <= 0 || puslapiai > likoPuslapiu) {
            return false;
        }

        likoPuslapiu -= puslapiai;
        return true;
    }

    public static void main(String[] args) {
        Knyga knyga = new Knyga();

        System.out.println(knyga.skaityti(20));
        System.out.println(knyga.getLikoPuslapiu());
        System.out.println(knyga.skaityti(100));
        System.out.println(knyga.getLikoPuslapiu());
    }
}
Konsolės rezultatas

true
76
false
76

  1. Knygoje pradžioje likę 96 puslapiai.
  2. skaityti(20) yra tinkamas veiksmas, todėl metodas atima 20, palieka 76 ir grąžina true.
  3. Tada bandoma perskaityti 100 puslapių, nors liko tik 76.
  4. Metodas atmeta veiksmą, grąžina false ir atributo nekeičia.
  5. Todėl po abiejų bandymų likusių puslapių skaičius vis dar yra 76.
skaityti() išreiškia prasmingą objekto veiksmą.

Bendras setteris leistų išoriniam kodui savavališkai nustatyti likusių puslapių skaičių ir apeiti skaitymo taisykles.

Nekintama ir tik skaitoma būsena

Jei reikšmė po objekto sukūrimo neturi keistis, galima naudoti private final lauką ir pateikti tik getterį:

public class Knyga {
    private final String isbn;

    public Knyga(String isbn) {
        if (isbn == null || isbn.isBlank()) {
            throw new IllegalArgumentException("ISBN privalomas");
        }

        this.isbn = isbn;
    }

    public String getIsbn() {
        return isbn;
    }

    public static void main(String[] args) {
        Knyga knyga = new Knyga("978-609-01-0000-0");
        System.out.println(knyga.getIsbn());
    }
}
Konsolės rezultatas

978-609-01-0000-0

  1. ISBN perduodamas konstruktoriui kuriant knygos objektą.
  2. Konstruktorius atmeta tuščią arba null reikšmę.
  3. Patikrinta reikšmė išsaugoma privačiame ir nekintamame final atribute.
  4. Viešas getteris leidžia ISBN perskaityti.
  5. Setterio nėra, todėl išorinis kodas negali paprašyti klasės ISBN pakeisti.

Atsargiai su keičiamais objektais

Vien private nepakanka, jei getteris grąžina keičiamo objekto nuorodą. Kolekcijai galima grąžinti nekintamą kopiją:

import java.util.ArrayList;
import java.util.List;

public class Knyga {
    private final List pastabos = new ArrayList<>();

    public void pridetiPastaba(String pastaba) {
        pastabos.add(pastaba);
    }

    public List getPastabos() {
        return List.copyOf(pastabos);
    }

    public static void main(String[] args) {
        Knyga knyga = new Knyga();
        knyga.pridetiPastaba("Perskaityti dar kartą");

        List kopija = knyga.getPastabos();
        System.out.println(kopija);
    }
}
Konsolės rezultatas

[Perskaityti dar kartą]

  1. Vidinis pastabų sąrašas yra private, todėl jį valdo pati Knyga klasė.
  2. Pastaba pridedama per viešą metodą pridetiPastaba().
  3. Getteris negrąžina tiesioginės vidinio sąrašo nuorodos.
  4. List.copyOf(pastabos) sukuria nekintamą sąrašo kopiją.
  5. Išorinis kodas gali perskaityti pastabas, bet negali per šią kopiją slapta pakeisti vidinio sąrašo.

Vieša sąsaja ir vidinė realizacija

Inkapsuliuotos klasės dalys
DalisPavyzdžiaiPaskirtis
Vieša sąsajapublic metodaiLeidžiami veiksmai klasės naudotojams
Vidinė realizacijaprivate laukai ir pagalbiniai metodaiDuomenys ir detalės, kurias valdo pati klasė

Praktinės gairės

  • Laukus pagal nutylėjimą laikyk private.
  • Viešink elgseną, o ne visas vidinės būsenos keitimo galimybes.
  • Objekto taisykles tikrink konstruktoriuose ir būseną keičiančiuose metoduose.
  • Nekurk setterio vien todėl, kad egzistuoja laukas.
  • Negrąžink vidinių keičiamų kolekcijų tiesioginių nuorodų.