Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!
⛔ Masz dość walki z kodem i samym sobą? 🔄 Czas na RESET! ✅ Dołącz do bezpłatnego wyzwania!
Wyobraź sobie posiadanie własnego pełnomocnika. W moich marzeniach taka osoba szukałaby dla mnie najlepszych promocji. Mogłaby segregować moją pocztę i przekazywać mi tylko najważniejsze maile. Niestety nie znam nikogo, kto ma komfort posiadania takiego pomocnika. Znam za to wielu programistów, którzy korzystają ze swoich Pełnomocników w kodzie.
Pełnomocnik to obiekt, który jest tworzony w miejsce pierwotnego obiektu. Najczęściej wykonuje on „brudną” robotę i angażuje obiekt pierwotny tylko wtedy, gdy jest rzeczywiście potrzebny. Jeśli takie pojęcia jak lazy initialization, pamięć podręczna czy walidacja nie są Ci obce, to na pewno spotkałeś się z Proxy!
Proxy to strukturalny wzorzec projektowy. Wzorce strukturalne wyjaśniają, jak składać obiekty i klasy w większe struktury, zachowując przy tym ich elastyczność i efektywność.
Proxy to obiekt tworzony w miejsce obiektu pierwotnego. Co więcej, powinien on być wykorzystywany zamiast obiektu pierwotnego i rozszerzać go o dodatkowe pola czy metody.
Rozszerzanie skojarzyło Ci się z dziedziczeniem? Słusznie. W tym przypadku Proxy i obiekt pierwotny dziedziczą ten sam interfejs. Oznacza to, że Proxy i obiekt pierwotny mają te same metody – ale mimo to robią różne rzeczy. Proxy jest pośrednikiem między miejscem wywołania akcji a jej wykonania. Wykonuje różne dodatkowe czynności, zanim właściwa akcja zostanie wywołana.
Myślę, że najlepiej będzie posłużyć się przykładem z życia i diagramem UML. Jeśli jeszcze nie wiesz, jak stworzyć diagram i na czym polegają relacje między obiektami, to zachęcam Cię do zapoznania się z moim poprzednim artykułem: „Podstawy UML – diagramy klas”.
Niech naszym obiektem pierwotnym będą drzwi. Drzwi, jak to mają w zwyczaju, służą do otwierania pomieszczenia (metoda open). Jednak gdyby nasze mieszkania nie były wyposażone w zamki na klucze, nie byłyby tak bezpieczne. Dlatego drzwi z zamkiem potraktuję jako jedną całość i nazwę antywłamaniowymi. Będą stanowić Proxy. Robią one to samo, co zwykłe drzwi (implementacja interfejsu DoorInterface), ale dodatkowo korzystają z metody checkKeys w celu sprawdzenia, czy otwieramy mieszkanie odpowiednimi kluczami. Spójrz na diagram ilustrujący ten wzorzec:
Po analizie przykładu wzorzec Proxy powinien być dużo bardziej zrozumiały.
Warto korzystać z Proxy wtedy, gdy nie chcemy od razu wywoływać metody z obiektu pierwotnego. Możesz go użyć np. w takich działaniach jak:
Zastosujmy wzorzec w kodzie JavaScript. Posłużę się przedstawioną wcześniej analogią. Na początku stworzę interfejs DoorInterface, który będzie udostępniał metodę open. Jeśli nie znasz interfejsów, zapraszam Cię do zapoznania się z artykułem „4 filary programowania obiektowego”.
export default class DoorInterface {
open() {
throw new Error('Not implemented');
}
}
Teraz stwórzmy oryginalną klasę Door.
import DoorInterface from './interface/DoorInterface.js';
export default class Door extends DoorInterface {
#isOpened = false;
open() {
this.#isOpened = true;
console.log(`Door is ${this.#isOpened && 'opened 😁'}`);
}
}
Implementuje ona wcześniej zdefiniowany interfejs. Tworzymy metodę open, która informuje nas, czy udało się otworzyć drzwi.
Zwróć uwagę, że obiekty tego typu mogą już istnieć w Twoim projekcie. Aby kontrolować dostęp do nich, stwórz im pełnomocnika!
Zgodnie z diagramem naszym Proxy ma być klasa AntiBurglarDoor. Wykorzystamy ten sam interfejs, ale dostęp do metody open będzie obwarowany przez metodę checkKeys.
import DoorInterface from './interface/DoorInterface.js';
import Door from './Door.js';
export default class AntiBurglarDoor extends DoorInterface {
#originalDoor;
constructor(originalDoor) {
super();
if (!(originalDoor instanceof Door)) {
throw new Error(
'The original door must be an instance of DoorInterface'
);
}
this.#originalDoor = originalDoor;
}
checkKeys() {
// nie mamy właściwych kluczy
const hasCorrectKeys = false;
return hasCorrectKeys;
}
open() {
// jeśli mamy właściwe klucze, to otwórz drzwi
if (this.checkKeys()) {
this.#originalDoor.open();
return;
}
console.log('Door closed 😲 You do not have the correct keys');
}
}
W metodzie checkKeys „na sztywno” zdefiniowaliśmy zmienną hasCorrectKeys z wartością false. W Twoim projekcie powinna znaleźć się tam odpowiednia logika biznesowa, która stanowi istotę pełnomocnika.
Stworzone klasy użyję w głównym pliku projektu – index.js.
import Door from './Door.js'; import AntiBurglarDoor from './AntiBurglarDoor.js'; const door = new Door(); const antiBurglarDoor = new AntiBurglarDoor(door); // korzystam z oryginalnego obiektu door.open(); // Door is opened 😁 // korzystam z Proxy antiBurglarDoor.open(); // Door closed 😲 You do not have the correct keys
Nasz pełnomocnik spełnił swoją funkcję – zablokował dostęp do pomieszczenia, sprawdzając poprawność klucza.
Kod z przedstawionych przykładów możesz podejrzeć w repozytorium.
Największa do tej pory aktualizacja ECMAScript, czyli ES6, przyniosła ze sobą dużo praktycznych nowości. Jedną z nich jest możliwość korzystania z obiektu Proxy, który obecnie cieszy się niemal całkowitym wsparciem każdej z przeglądarek.
Proxy to nowy konstruktor globalny, który przyjmuje dwa argumenty:
Tak jak w przypadku wzorca Proxy, tworzymy obiekt-pomocnika, wykorzystujący obiekt pierwotny. Dzieje się to za pomocą słowa kluczowego new Proxy(). Pomocnik dziedziczy wszystkie metody od obiektu pierwotnego. Aby skorzystać z wbudowanego obiektu Proxy, powinieneś wykorzystać następującą strukturę:
import OriginalObject from './OriginalObject.js';
const originalObject = new OriginalObject();
const originalObjectProxy = new Proxy(originalObject, {
get(object, key) { },
set(object, key, newValue) { }
})
Jak widzisz, pierwszym parametrem new Proxy() jest obiekt pierwotny, drugim zaś obiekt handler – zawiera on metody get oraz set zwane też pułapkami (traps). To właśnie za pomocą tych dwóch metod możesz kontrolować pobieranie i ustawianie właściwości w oryginalnym obiekcie. Dokumentacja przedstawia również inne pułapki, z którymi możesz się zapoznać. Ja zostanę przy tych dwóch, gdyż w zupełności mi wystarczą.
Zaprezentuję Ci wykorzystanie obiektu Proxy. Posłużymy się przykładem, w którym odpytamy opensourcowe API. Jego zadaniem jest podawanie informacji o pogodzie. Nasz obiekt pierwotny będzie miał za zadanie:
Stworzymy również pełnomocnika. Jego rolą będzie kontrola czasu, w którym pobrano informację o pogodzie za pomocą obiektu pierwotnego. Jeśli czas między kolejnymi pobraniami będzie krótszy niż 10 sekund, to zatrzymamy ten proces. Czas mogę zapisywać np. za pomocą zmiennej, którą przechowam w localStorage. Do dzieła!
Aby iść zgodnie z duchem OOP, stworzę interfejs AskWeatherInterface.js:
export default class AskWeatherApiInterface {
getWeather() {
throw new Error('Not implemented');
}
}
Mówi nam on, że klasa potomna musi zaimplementować metodę getWeather.
Klasa-dziecko to WeatherApi.js. Za pomocą fetch oraz async/await pobierzemy informacje o aktualnych warunkach pogodowych:
import AskWeatherApiInterface from './interface/AskWeatherApiInterface.js';
export default class WeatherApi extends AskWeatherApiInterface {
#latitude;
#longitude;
constructor(latitude, longitude) {
super();
this.#latitude = latitude;
this.#longitude = longitude;
}
async getWeather() {
const url = `https://api.open-meteo.com/v1/forecast?latitude=${
this.#latitude
}&longitude=${this.#longitude}¤t_weather=true`;
const response = await fetch(url);
const data = await response.json();
const weather = data.current_weather;
return weather;
}
}
Dwa pola prywatne #latitude oraz #longitude określają lokalizację, z której pobierzemy pogodę. Zbudowaliśmy metodę getWeather, która zwraca obiekt weather. Prezentuje się on tak:
Pozostaje nam stworzyć pomocnika, który będzie pilnował, by requesty do API nie były częstsze niż co 10 sekund. Korzystam z obiektu Proxy i tworzę plik withProxy.js:
const WAIT_TIME = 1000 * 10;
const canCallApiAgain = () => {
const lastCallTime = localStorage.getItem('callTime');
if (!lastCallTime) {
return true;
}
const currentTime = new Date().getTime();
return currentTime - lastCallTime > WAIT_TIME;
};
const logCallTime = () => {
const currentTime = new Date().getTime();
localStorage.setItem('callTime', currentTime);
};
const withProxy = weatherApiObject =>
new Proxy(weatherApiObject, {
get(object, key) {
if (key === 'getWeather') {
if (!canCallApiAgain()) {
throw new Error(
'You can call API only once per 10 seconds'
);
}
logCallTime();
return async function () {
const weather = await object[key]();
return weather;
};
}
}
});
export default withProxy;
Funkcje canCallApiAgain oraz logCallTime stanowią funkcje pomocnicze. Pierwsza sprawdza, czy minęło 10 sekund od ostatniego requestu do API, a druga za pomocą klucza callTime zapisuje czas ostatniego calla w localStorage.
Istotą jest funkcja withProxy. Za pomocą new Proxy tworzymy pomocnika obiektu weatherApi. Przekażemy go w parametrze, gdy będziemy pisać kod w głównym pliku index.js. Pułapka get(object, key) sprawdza, czy możemy użyć któregoś z kluczy (key) w oryginalnym obiekcie (object). Jeśli klucz to getWeather – wywołujemy funkcję o tej nazwie. Właśnie w tym miejscu sprawdzam, czy możemy ponownie odpytać API. Jeśli tak, to zapisujemy czas odpytania przez wywołanie logCallTime i prosimy o dane. Jeśli nie, rzucamy błąd.
Zamiast rzucania błędu moglibyśmy również przekazywać ostatnio pobraną odpowiedź. Wykonaj to jako zadanie dodatkowe – to dobry sposób na przećwiczenie Twojej znajomości Proxy!
Przejdźmy do ostatniej części kodu, czyli użycia klasy WeatherApi oraz funkcji withProxy. Tworzę plik index.js:
import WeatherApi from './WeatherApi.js';
import withProxy from './withProxy.js';
const weatherApi = new WeatherApi(52.23, 21.01);
withProxy(weatherApi).getWeather().then(weather => {
console.log(weather);
});
Gotowe! Stworzyliśmy obiekt odpytujący API o pogodę. Jest on chroniony przez Proxy. Kod przedstawiony powyżej znajdziesz w repozytorium na GitHubie.
Wiesz już, czym jest wzorzec Proxy. Teraz będziesz mógł go wykorzystać, by:
To tylko kilka przykładów zastosowania pełnomocnika. Może znajdziesz dla niego odpowiednie miejsce w swoim projekcie?
Udostępnij ten artykuł:
Chcesz zostać (lepszym) programistą i lepiej zarabiać?
🚀 Porozmawiajmy o nauce programowania, poszukiwaniu pracy, o rozwoju kariery lub przyszłości branży IT!
Umów się na ✅ bezpłatną i niezobowiązującą rozmowę ze mną.
Chętnie porozmawiam o Twojej przyszłości i pomogę Ci osiągnąć Twoje cele! 🎯