Zasady programowania obiektowego i hermetyzacja danych w JavaScript

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, 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 technik:

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 obiety 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

Hermetyzacja polega na ukrywaniu wewnętrznej implementacji obiektu. Dzięki takiemu podeściu nie tylko uzyskuje się prosty dostęp do obiektu za pomocą wyodrębnionego interfejsu, ale również uodparnia na błędy tworzonego modelu.

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ą słów kluczowych takich jak public i private. W świecie JavaScriptu 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.