Kadr z filmu "Zabójcza broń"
Jeśli spojrzymy na definicje wzorców które dotychczas udało nam się poznać, to formułka opisująca Proxy jest z nich wszystkich najkrótsza. Dość ironicznie, bo „Pośrednik” jest również najtrudniejszy.
Zacznijmy zatem od teorii:
Proxy – zastępuje inny obiekt i kontroluje dostęp do niego.
Rusz głową! Wzorce projektowe
Tyle. Osiem słów. A jak przyjdzie co do czego to w grę wchodzą rejestry sieciowe, buforowanie, firewall, Reflection API i sporo innych trudnych słówek.
Definicja wzorca trochę mylnie sugeruje podobieństwo do Adaptera, który też przecież zastępuje inny obiekt. Różnica tkwi w tym, że Adapter robi to TYLKO po to aby zniwelować różnicę w interfejsach, natomiast Proxy ma inne, znacznie bardziej zróżnicowane zastosowania. Dla przykładu:
- RemoteProxy pośredniczy w wymianie informacji pomiędzy klientem i serwerem
- VirtualProxy pośredniczy w dostępie do obiektów, których utworzenie wymaga sporo czasu/mocy obliczeniowej
- ProtectionProxy chroni obiekt przed nieupoważnionym dostępem
- CachingProxy buforuje obiekty z których często korzystamy
- SynchronizationProxy zapewnia bezpieczny dostęp do obiektu w środowisku wielowątkowym
A to dopiero początek listy. Jeśli chciałbym opisać każdy rodzaj pośrednika musiałbym poświęcić na to tyle samo artykułów ile dotąd stworzyłem z tej serii. I kto wie, może to kiedyś nastąpi, ale dzisiaj zajmiemy się chyba najprostszym z nich – ProtectionProxy, który w swojej podstawowej implementacji do złudzenia przypomina coś co już przerabialiśmy: wzorzec Dekorator.
Ale po kolei…
W latach 80-tych i 90-tych swoje triumfy święciły filmy akcji o parach „gliniarzy” (tzw. Buddy Cop, choć nie zawsze byli stricte policjantami) na tropie wielkich afer. „Zabójcza Broń” z obrazka tytułowego to chyba najbardziej rozpoznawalny przykład, ale były też takie perełki jak „Tango i Cash”, „Bad Boys”, „Faceci w czerni”, „Starsky & Hutch” czy moje ulubione „Godziny szczytu”.

Kumple zawsze byli łączeni ze sobą na zasadzie przeciwieństw. Flirciarz i przykładny małżonek, poważny i wyluzowany, zorganizowany i spontaniczny. Prowadziło to do wielu zabawnych sytuacji, na początku współpraca przebiegała opornie, a w końcówce obaj bohaterowie ratowali sobie na zmianę tyłki.
Podobnie jest z ProtectionProxy i obiektem któremu pośredniczy.
W wielu z wymienionych powyżej produkcji (o ile nie we wszystkich) znajduje się scena przesłuchania podejrzanego. Pojawia się motyw:

Ten zły krzyczy, grozi, szarpie i dusi bandytę, próbując wydobyć z niego informacje. Jeśli to nic nie daje, wtedy pojawia się ten dobry, przynosi jedzenie, poprawia zatrzymanemu marynarkę i zatroskanym tonem prosi o współpracę obiecując ulgowe traktowanie. Nawiązuje się nić porozumienia, bandzior daje za wygraną i opisuje dokładnie gdzie tego wieczoru nastąpi transakcja.
Ale żeby dojść do tego momentu, gangster wpierw musi zostać „zmiękczony” przez złego glinę. Naszego pośrednika ProtectionProxy (PP).
Zwykły obiekt ma swoje metody i zrobi dla nas co tylko chcemy – to ten dobry glina. Zadaniem PP (złego gliny) jest dopuszczenie do niego tylko konkretnego klienta (bandziora z ważnymi informacjami).
Na dzisiaj przygotowałem dwa przykłady jak działanie PP mogłoby wyglądać w kodzie. Wersja pierwsza (kod tutaj) jest łatwiejsza do ogarnięcia – to nic więcej aniżeli dostosowany Dekorator, który zamiast dodawać nowe metody blokuje dostęp do tych, które obiekt dekorowany posiada.
Druga wersja (kod) w działaniu nie różni się od poprzedniczki, ale oparta została na tzw. DynamicProxy czyli Proxy tworzonym już w czasie działania programu za pomocą instrukcji w klasie implementującej interfejs InvocationHandler.
Ale zacznijmy od tej prostszej:

Sprawa jest jasna – jeśli skrzynia jest „niechroniona” zarówno czarodziej jak i łotrzyk mają do niej swobodny dostęp. Ale gdy opakujemy skrzynię w PP, już tylko łotrzyk – jako postać umiejąca otwierać zamki – jest uprawniony do jej otwarcia:
public void open(Character character) {
if (character instanceof Rogue){
System.out.println(character + " Udało się otworzyć zamek!");
treasure.open(character);
}
else System.out.println(character + " Skrzynia zamknięta. Potrzebujesz klucza albo wytrychów.");
}
Przykład jest banalny i możecie się zastanawiać, dlaczego tego if-a od razu nie dałem w klasie Chest. Cóż, w ten sposób hermetyzujemy warunek i nie musimy modyfikować klasy Skrzynia w przyszłości jeśli np. chcielibyśmy dodać czarodziejowi zaklęcie otwierania zamków. Czasami też jeden PP chroni dostęp do różnych obiektów, którym wystarczy wtedy dodać interfejs znacznikowy (bez metod).
Drugi kod robi to samo za pomocą klasy LockedChestHandler implementującej interfejs InvocationHandler z jedną metodą: invoke().

Nie powiedziałbym, że metoda jest zrozumiała już na pierwszy rzut oka, dlatego spróbujmy ją trochę rozjaśnić. Zapodajmy jednak wcześniej kod tworzący owe proxy z metody main():
Treasure lockedTreasure = (Treasure) Proxy.newProxyInstance(
treasure.getClass().getClassLoader(),
treasure.getClass().getInterfaces(),
new LockedChestHandler(treasure)
);
Zaczynamy od stworzenia instancji Proxy – nie za pomocą tradycyjnego konstruktora z „new„, ale osobną metodą przyjmującą trzy parametry:
- ClassLoader klasy/abstrakcji z jakiej pochodzi nasz obiekt
- listy interfejsów z jakich korzysta
- oraz odwołania do klasy „Handlera” którą stworzyliśmy wcześniej
ClassLoader to komponent JVM służący do „ładowania” klasy, uzyskujemy go poprzez Odbicie (Reflection API) podobnie jak listę interfejsów. Ważne jest również aby powstały w ten sposób PP przerzutować na docelowy typ (Treasure w naszym wypadku) bo metoda newProxyInstance() zwraca tylko i wyłącznie instancje klasy Obiekt.
if (method.getName().equals("open")) {
Character character = (Character) args[0];
if (character instanceof Rogue) {
System.out.println(character + " Udało się otworzyć zamek!");
return method.invoke(treasure, args);
} else {
System.out.println(character + " Skrzynia zamknięta. Potrzebujesz klucza albo wytrychów.");
return null;
}
}
return null;
Kiedy już wrzucimy do metody potrzebne parametry, dynamiczne proxy jest budowane wedle wzoru z metody invoke() – nie wywołujemy jej samodzielnie. Jej parametry: (Object proxy, Method method, Object[] args) są uzyskiwane z załączonego wcześniej ClassLoadera oraz interfejsów.
Object proxy to obiekt dla którego tworzymy pośrednika. U nas jest to klasa z metodą invoke() czyli LockedChestHandler. Ten parametr jest tam na wypadek gdyby owa klasa miała jeszcze jakieś własne metody do wywołania. Ale u nas nie ma więc tego obiektu w kodzie nie używamy.
Method method to metoda w klasie Chest z której chcemy skorzystać. Zauważmy warunek:
if (method.getName().equals("open"))
To oznacza, że tylko wywołanie metody open() będzie szło przez pośrednika! Wywołanie jakichkolwiek innych metod zwróci wartość null.
Object[] args to z kolei parametry jakie przyjmuje nasza metoda open(). W przypadku skrzyni to tylko obiekt typu Character i to właśnie nań musimy rzutować aby stworzyć niezbędny warunek:
Character character = (Character) args[0];
if (character instanceof Rogue) ...
Pamiętajmy też, że metoda invoke() zwraca typ Object, dlatego musimy albo zwrócić null jeśli warunek nie przejdzie, albo:
return method.invoke(treasure, args);
Uff. Koniec.
ITCandidateEvaluator
W założeniu mój drugi samodzielny projekt ma powstać w dwóch wersjach. Ta, za którą wezmę się już niebawem ma działać lokalnie i w wersji okienkowej. Tutaj nie będą potrzebni mi żadni pośrednicy.
Wersja 2.0 którą mam nadzieję stworzę w nieokreślonej przyszłości ma być aplikacją webową. W tym wypadku jest spora szansa, że jakieś Proxy może się pojawić. Nie wiem tylko czy Spring, którego jeszcze nie tknąłem, nie załatwi pośredników za mnie. Przy okazji – o rzeczonym frameworku też chcę wyskrobać parę artykułów, jak tylko będę na to gotowy.
Proxy był ostatnim ze standardowych wzorców opisanych w książce Rusz głową! Co wcale nie oznacza, że dziś kończymy przygodę – zostały jeszcze wzorce dodatkowe, omówione pobieżnie, z których już wybrałem Buildera na temat kolejnego odcinka. Jest zwyczajnie zbyt ważny aby go pominąć.
Do następnego!