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.
Trening czyni mistrza
Jeżeli chcesz sobie potrenować zdobytą wiedzę, zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania