W jednym z poprzednich wpisów omówiłem podstawowe sposoby tworzenia obiektów w języku JavaScript. W tym wpisie omówię warunki spełniane przez JavaScript, dzięki którym możemy mówić o nim jako o obiektowym języku programowania.
Programowanie obiektowe w JavaScript
Programowanie obiektowe, to jeden z najpopularniejszych modeli programowania. Aby móc określić język programowania jako obiektowy, musi on posiadać pewne cechy. Podstawowym warunkiem jest umożliwienie przedstawienia programu za pomocą obiektów. Drugim – hermetyzacja i ponowne wykorzystanie kodu.
Spełnienie pierwszego warunku, wiąże się z użyciem wykorzystaniem następujących :
Asocjacja
Jest rodzajem relacji pomiędzy obiektami, w której obiekty są od siebie niezależne. Oznacza to, że usunięcie związku pomiędzy obiektami nie wpływa na ich sposób funkcjonowania. Poniższy kod przedstawia utworzenie właśnie takiej relacji:
function Person(name) {
this.name = name;
this.phone = null;
}
function Phone(phone) {
this.phone = phone;
}
const john = new Person('John');
const phone = new Phone('iPhone');
john.phone = phone;
W powyższym przykładzie utworzyliśmy związek asocjacji pomiędzy dwoma obiektami. Istnienie obiektu john
nie jest warunkiem istnienia obiektu phone
, ani odwrotnie.
Agregacja
Agregacja jest szczególnym przypadkiem asocjacji. Dotyczy ona sytuacji, w której jeden obiekt pełni funkcję nadrzędną względem drugiego:
const university = {
students: []
};
const john = new Person('John', 'Smith');
university.students.push(john);
W powyższym przykładzie obiekty typu Person
pomagają w budowaniu obiektu university
. W przypadku zamknięcia uniwersytetu, studenci nie przestaną istnieć. Innymi słowy agregacja polega na tworzeniu obiektu składającego się z innych obiektów, które są związane z obiektem nadrzędnym, jednak mimo to mogą istnieć niezależnie.
Kompozycja
Kompozycja jest z kolei szczególnym przypadkiem agregacji. W tym przypadku żaden obiekt składowy nie może istnieć poza swoim obiektem nadrzędnym. Ponownie spójrzmy na przykład:
const john = {
firstName: "John",
address: {
street: "Piłsudskiego 23",
city: "Wrocław"
}
};
Obiekt reprezentujący adres w tej sytuacji jest ściśle związany z obiektem john
i jego istnienie w przypadku skasowania obiektu nadrzędnego nie ma sensu.
Zasady programowania obiektowego
Drugim z wymogów dotyczących obiektowych języków programowania, jest przestrzeganie zasad:
- Hermetyzacji i ukrywania informacji
- Dziedziczenia
- Polimorfizmu
W tym artykule skupię się na pierwszym punkcie, oraz przedstawię przykład implementujący tą zasadę z wykorzystaniem struktury WeakMap.
Hermetyzacja
Krótko mówiąc hermetyzacja polega na ukrywaniu wewnętrznej implementacji obiektu. Poprzez to podejście nie tylko uzyskuje się prosty dostęp do obiektu za pomocą wyodrębnionego interfejsu, ale również uodparnia na błędy tworzonego modelu.
Dzięki hermetyzacji możemy ukryć pewne informacje przed innymi częściami programu, ograniczając bezpośredni dostęp do nich. Zazwyczaj możemy to osiągnąć poprzez stosowanie modyfikatorów dostępu, takich jak public, private i protected, które określają, które pola i metody są widoczne dla innych obiektów i które są ukryte.
Pewną niedogodnością podczas stosowania obiektowych metod programowania w języku JavaScript, jest to, że obiekty nie mają prywatności. W innych obiektowych językach, takich jak Java, zasadę hermetyzacji można wdrażać za pomocą odpowiednich słów kluczowych. W przypadku JavaScript wszystkie składowe obiektu są publiczne.
Jednak nic nie stoi na przeszkodzie, żeby zaimplementować taki mechanizm. Poniżej przedstawiam jeden ze sposobów na zarządzanie prywatnością wewnątrz klasy z wykorzystaniem struktury WeakMap.
// Person.js
const _email = new WeakMap();
class Person {
constructor(firstName, lastName, email) {
_email.set(this, email);
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
getEmail() {
return _email.get(this);
}
}
export default Person;
// index.js
import Person from './Person';
const john = new Person('John', 'Smith', 'john.smith@example.com');
console.log( john.getEmail() ); // "john.smith@example.com"
W powyższym przykładzie utworzyliśmy prywatną składową email
z wykorzystaniem nowego elementu ES6 WeakMap. Jest to kolekcja par klucz-wartość, w której kluczem jest obiekt. Wykorzystanie tej struktury w tym przypadku jest związane z cechą WeakMap, którą jest to, że przechowuje ona słabą referencję do klucza. Oznacza to, że gdy obiekt zostanie usunięty, jego składowe prywatne zostaną skasowane wraz z nim, dzięki czemu nie będą niepotrzebnie zajmować pamięci.