Pełnomocnik

Wzorce projektowe

  |   8 min czytania

Zastosowanie

Pełnomocnik (ang. Proxy) (wzorzec strukturalny) tworzy obiekt, którego zadaniem jest reprezentowanie (pełnienie pełnomocnictwa) innego obiektu. Na podstawie stanu systemu i spełnienia warunków obiekt klasy pełnomocnika Proxy, decyduje o utworzeniu bądź nie instancji klasy obiektu docelowego i wywołaniu na nim odpowiednich metod. Takie podejście zapewnia kontrolowane tworzenie kosztownych obiektów oraz kontrolę dostępu. W przypadku nie spełnienia warunków (np.: błędne hasło) pomijany jest proces tworzenia kosztownego obiektu i wywołania na nim metody, która nie może zostać wykonana zgodnie z oczekiwaniami systemu czy użytkownika. Wzorzec ten umożliwia realizowanie dodatkowych zadań w ramach emulowanego obiektu.

Ograniczenia

Wykorzystując wzorzec Pełnomocnik, należy mieć na uwadze, że poza oczywistymi korzyściami wynikającymi z późnego inicjalizowania i dodatkowego sprawdzania warunków można odnotować opóźnienie w odpowiedzi czy wykonaniu zadania. Wzorzec ten jest podobny w strukturze do Dekoratora, jednakże Pełnomocnik zarządza cyklem życia obiektów emulowanych, natomiast struktura obiektu klasy implementującego wzorzec Dekorator jest zarządzana przez użytkownika. Ponadto ze względu na opakowanie inicjalizowania obiektu i wywołania jego metod może przypominać wariacje wzorców Fasada oraz Adapter. Ze względu na podobieństwa do innych wzorców decyzja o wyborze wzorca Pełnomocnik może nie być trywialna.

Użycie

Pełnomocnik jest wykorzystywany przede wszystkim w sytuacjach, gdzie należy zapewnić kontrolę dostępu do obiektu (np. proces autoryzacji). Znajduje również zastosowanie w procesie tworzenia kosztownych obiektów (np. dostęp do bazy danych) oraz emulacji zdalnego obiektu (np. przesyłanie danych).

Implementacja

Klasa pełnomocnika Proxy oraz klasa RealSubject, której obiekt udziela pełnomocnictwa implementują interfejs Subject. Klasa Proxy zawiera prywatne pole typu Subject, które w zależności od ustalonych warunków jest inicjalizowane instancją klasy RealSubject. Klasa Proxy nadpisuje metody interfejsu w taki sposób, że wywołują one odpowiadające im metody obiektu klasy RealSubject.

Pełnomocnik diagram

Poniższy listing przedstawia implementacje wzorca Pełnomocnik dla klasy RealSubject w oparciu o proces autoryzacji.

public class Proxy implements Subject {

    private Subject subject;
    private String password;

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public void action() {
    	if(password != null && password.equals("password")) {
    		if(subject == null)
    		    subject = new RealSubject();
            subject.action();
        }
        else {
        	//show message or do something else
        }
    }
}

public class RealSubject implements Subject {

    @Override
    public void action() {
        //do something
    }
}

interface Subject {

    void action();
}

Klient inicjalizuje obiekt typu Subject instancją klasy Proxy, a następnie wywołuje na nim żądane metody.

Subject subject = new RealSubject();
subject.action(); //always execute

Subject proxy = new Proxy();
proxy.action(); //will not effect
proxy.setPassword("password");
proxy.action(); //do action

Przykład

Aplikacja Tasker umożliwia przechowywanie i zarządzanie listą zadań w trybie online i offline oraz współdzielenia zasobów w obrębie zespołu. Każdy użytkownik zespołu może przeglądać listę zadań, jednak tylko autoryzowani użytkownicy mogą dodawać lub usuwać zadania. Gdy system nie ma połączenia z internetem wówczas ładowane są dane z pamięci podręcznej aplikacji. W celu optymalizacji zużycia kosztownych zasobów (tworzenie obiektów) oraz pakietu danych, aplikacja wykorzystuje wzorzec Pełnomocnik do sprawdzenia stanu połączenia internetowego oraz autoryzacji użytkownika, co przedstawia poniższy listing.

public class TaskManagerProxy implements TaskManager {

    @Override
    public List<Task> getTasks() {
    	if(isNetworkActive()) {
            init();
            return taskManager.getTasks();
    	}
    	else {
            //try to get the last saved task list from cache
            List<Task> tasks = getTaskFromCache();
            if(tasks.isEmpty()) {
                //show message about any tasks available
            }
            else {
                //show message about no internet connection, so tasks are not up to date
            }
            return tasks;
        }
    }

    @Override
    public void addTask(Task task) {
        if(isNetworkActive()) {
            if(isAccessProvided()) {
                init();
                taskManager.addTask(task);
            }
            else {
            	//show message about user authorization
            	addTaskToCache(task);
            }
        }
        else {
            //show message about no internet connection
            addTaskToCache(task);
        }
    }

    @Override
    public void deleteTask(Task task) {
        if(isNetworkActive()) {
            if(isAccessProvided()) {
                init();
                taskManager.deleteTask(task);
            }
            else {
                //show message about user authorization
                deleteTaskFromCache(task);
            }
        }
        else {
            //show message about no internet connection
            deleteTaskFromCache(task);
        }
    }

    private boolean isNetworkActive() {
        //check network connection state
        return true; //mock
    }

    private boolean isAccessProvided() {
    	//check is user authorized
    	return true; //mock
    }

    private void init() {
    	if(taskManager == null)
    	    taskManager = new TaskManager();
    }

    private List<Task> getTaskFromCache() {
    	List<Task> tasks = new ArrayList();
    	//get tasks from Cache
    	return tasks;
    }

    private void addTaskToCache(Task task) {
    	//add task to Cache
    	//add task to "resend list" in Cache
    }

    private void deleteTaskFromCache(Task task) {
    	//delete task from Cache
    	//add task to "deleted list" in Cache
    }
}

public class NetworkTaskManager implements TaskManager {

    private Network network;

    public NetworkTaskManager() {
        network = new Network();
        //set network server, port, etc
    }

    @Override
    public List<Task> getTasks() {
        //prepare and send request
        network.setOperation("GET");
        return network.getTasks();
    }

    @Override
    public void addTask(Task task) {
        //prepare and send request with
        network.setOperation("POST");
        network.send(task.toJson());
    }

    @Override
    public void deleteTask(Task task) {
        //prepare and send request
        network.setOperation("DELETE");
        network.send(task.toJson());
    }
}

interface TaskManager {

    List<Task> getTasks();
    void addTask(Task task);
    void deleteTask(Task task);
}

Sposób wykorzystania Pełnomocnika dla TaskManager może przebiegać następująco.

//login to app as authorized user
TaskManager taskManager = new TaskManagerProxy();
List<Task> tasks = taskManager.getTasks();
//show tasks in UI

//delete some task
taskManager.deleteTask(tasks.get(0));

//prepare task
Task task = new Task("name", "description", Priority.HIGH);
//internet connection lost
taskManager.addTask(task); //task will be add only to cache

Biblioteki

Ze względu na prostotę wzorca opartą o implementacje wspólnego interfejsu, biblioteki implementujące wzorzec Pełnomocnik nie mają sensu. Przykładem realizacji wzorca jest klasa Proxy wraz z InvocationHandler z pakietu java.lang.reflect.