Hermetyzacja w Javascript

Hermetyzacja (zwana też enkapsulacją) to kolejne pojęcie nierozłącznie związane z programowaniem obiektowym. Polega ono na rozdzieleniu wewnętrznego i zewnętrznego interfejsu naszego obiektu.

Wyobraź sobie, że robisz dość skomplikowany mechanizm - np. machinę produkującą kebaby. Obiekt taki będzie miał pełno właściwości i metod, które będą działać w jego wnętrzu.

Ty jako programista raczej byś nie chciał, by inni programiści mogli grzebać w każdej składowej takiego obiektu, ponieważ potencjalnie mogli by coś popsuć, przez co nasze dzieło przestało by prawidłowo działać.

Tu właśnie pojawia się pojęcie hermetyzacji, która polega na odpowiednim ukrywaniu pewnych składowych przed zewnętrznym środowiskiem. Nasz obiekt może z nich korzystać, natomiast zewnętrzne środowisko może używać tylko rzeczy, które my mu świadomie udostępnimy.

Prywatne i publiczne

W programowaniu zorientowanym obiektowo metody i właściwości możemy podzielić na dwie grupy:

  • prywatne - mają do nich dostęp tylko metody danego obiektu
  • publiczne - mają do nich dostęp metody danego obiektu, ale i zewnętrzne środowisko

W niektórych językach do takiej klasyfikacji używa się słów kluczowych private i public. Poniżej zamieszczam zapożyczony z Wikipedii przykład klasy z Java:


class KontoBankowe {
    private TypPieniedzy saldo;

    public KontoBankowe(TypPieniedzy saldoPoczatkowe) {
        saldo = saldoPoczatkowe;
    };

    public KontoBankowe() {
        KontoBankowe(0);
    };

    public boolean wplac( TypPieniedzy kwota ) {
        if ( kwota > 0 ) {
            saldo += kwota;
            return true;
        }
        return false;
    }

    public boolean wyplac( TypPieniedzy kwota ) {
        // Powiększenie kwoty o 10% prowizji.
        TypPieniedzy kwotaProw = kwota*1.1;
        if ( ( kwotaProw > 0 ) && ( kwotaProw <= saldo ) ) {
            saldo -= kwotaProw;
            return true;
        }
        return false;
    }

    public TypPieniedzy podajStanKonta() {
    	return saldo;
    }
};

Widzisz modyfikatory public i private? To właśnie tymi słowami określamy która metoda i właściwość ma być prywatna, a która publiczna. W niektórych językach pojawia się też słowo protected, ale nie będę się tutaj na nim skupiał.

Hermetyzacja w Javascript

Do zabezpieczania swojego kodu możemy w Javascript podejść na kilka sposobów.

I teraz gdy utworzysz przykładową klasę:


//chcemy by poszczególne metody i właściwości obiektu były:

class SimpleClass {
    constructor(nr) {
        this.publicNumber = nr; //to publiczne
        this.privateNumber = 102; //to ma być prywatne
    }

    publicMethod() { //to ma być publiczne
        console.log(this.publicNumber);
    }

    privateMethod() {
        console.log(this.privateNumber); //to ma być prywatne
    }
}

domyślnie wszystkie jej metody i właściwości będą publiczne. Oznacza to, że w każdej chwili mogę w obiekcie tworzonym na jej bazie wszystko zmieniać.


const my = new SimpleClass(10);
my.privateNumber = "ala ma kota"; //nadpisałem właściwość
my.privateMethod = "ala ma kota"; //nadpisałem funkcję

Jak to rozwiązać?

Prywatne właściwości i metody w nowym Javascript

Od kilku lat Javascript mocno ewoluuje, a programiści coraz częściej za jego pomocą tworzą pokaźne aplikacje. Dlatego chcąc nie chcąc środowisko to musiało zaproponować oficjalne rozwiązania.

W najnowszych wersjach języka możemy już tworzyć w klasach metody i właściwości prywatne. Wystarczy, że poprzedzimy je znakiem #:


class SimpleClass {
    #privateNumber = 102; //prywatne musimy zadeklarować przed konstruktorem
    publicNumber = null; //publiczne możemy tak, ale spokojnie też w konstruktorze

    constructor(nr) {
        this.publicNumber = nr;
        this.#privateNumber = 102;
    }

    publicMethod() { //to ma być publiczne
        console.log(this.publicNumber);
    }

    #privateMethod() {
        console.log(this.#privateNumber);
    }
}

const my = new SimpleClass(10);

my.publicNumber = 103;
my.publicMethod = "xxx";
my.#privateMethod = "ala ma kota"; //błąd
my.#privateNumber = "ala ma kota"; //błąd

Hermetyzacja przez moduły

Kolejnym sposobem zabezpieczania naszego kodu jest stosowanie modułów, o których pomówimy tutaj. W każdym takim module wyznaczamy rzeczy, które zostaną wystawione poza dany plik. Dzięki temu inne pliki mają dostęp do rzeczy z danego pliku, które wystawiliśmy, natomiast nie mają dostępu do całej reszty kodu, który nie został przez nas wystawiony. Możemy to wykorzystać w naszym przypadku wyrzucając metody klasy poza jej ciało, dzięki czemu będzie miała do nich dostęp tylko nasza klasa, natomiast reszta plików zobaczy tylko klasę, którą właśnie wystawiliśmy.


//plik simple-class.js ----------
let privateNumber = 102;

function privateMethod() {
    console.log(privateNumber);
}

export class MyClass { //wystawiam klasę
    constructor(nr) {
        this.publicNumber = nr;
    }

    publicMethod() {
        console.log(this.publicNumber); //działa
        console.log(privateNumber); //działa
        privateMethod(); //działa
    }
}


//inny_plik.js ----------
import { MyClass } from "./simple-class.js";

const my = new MyClass();

my.publicMethod(); //działa
console.log(my.publicNumber); //działa

my.privateMethod(); //błąd
console.log(my.privateNumber); //błąd

Oznaczanie prywatnych właściwości

W starszych wersjach Javascript podziału na prywatne i publiczne właściwości nie było. Jednym ze sposobów rozwiązania tego zaganienia było poprzedzanie nazwy prywatnej składowej znakiem podłogi:


class SimpleClass {
    constructor(nr) {
        this.publicNumber = nr; //to publiczne
        this._privateNumber = 102; //to ma być prywatne
    }

    publicMethod() { //to ma być publiczne
        console.log(this.publicNumber);
    }

    _privateMethod() {
        console.log(this._privateNumber); //to ma być prywatne
    }
}

Konwencja ta nie tyczy się tylko klas, a o wiele częściej stosowana jest przy tworzeniu pojedynczych obiektów.

Dzięki niej osoba używająca naszego kodu będzie wiedziała, że danej składowej nie powinna ruszać spoza obiektu.


const my = new SimpleClass();

//tego nie powinienem
my._privateMethod();
my._privateNumber = "ala ma kota";

//to mogę
my.publicMethod();
my.publicNumber = "ala ma kota";

Konwencja ta nie zabezpiecza nam kodu, a tylko daje wskazówkę dla innych programistów. Podobnych konwencji mamy w Javascript kilka - nazwy konstruktorów czy klas piszemy z dużej litery, niektórzy programiści piszą nazwy stałych dużymi literami, pierwsze parametry funkcji w Node są miejscem na błędy, nadużywamy klas w html, bo nie umiemy css itp.

Zabezpieczanie przez zakresy zagnieżdżone

Kolejnym sposobem, który możesz spotkać w internecie to stosowanie zakresów zagnieżdżonych. Pamiętaj, że funkcje i zakresy zagnieżdżone mają dostęp do "rzeczy na zewnątrz", natomiast zewnętrzne środowisko nie ma dostępu do tego co jest wewnątrz danego zakresu/funkcji.

Przykład takiego podejścia pokazuje poniższy przykład. Jest to "klasyczne" zastosowanie wzorca modułu:


function myModule() {
    //prywatne właściwości i metoda
    let _numberA = 102;
    let _numberB = 10;

    function calculateNumbers() {
        console.log(_numberA + _numberB);
    }

    //zwracany obiekt ma dostęp do powyższych rzeczy
    //reszta skryptu nie ma do nich dostępu
    //obiekt ma oczywiście też dostęp do swoich własnych rzeczy przez this
    return {
        nrA : 302,
        nrB : _numberB,

        sum : calculateNumbers,

        doSomething() {
            console.log(_numberA);
            console.log(_numberB);
            calculateNumbers();
            console.log(this.nrA, this.nrB);
        }
    }
}


const my = myModule();

my.doSomething(); //działa
my.sum(); //działa
console.log(my.nrA); //302
console.log(my.nrB); //10

my.calculateNumbers(); //błąd
console.log(my._numberA); //błąd

Ten sposób pisania kodu wypróbujemy pisząc kod lightboxa.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem.
Aha - i ta strona korzysta z ciasteczek.