Wzorce Projektowe: Metoda Fabrykująca (Factory Method)

Definicja

Metoda fabrykująca (wytwórcza) jest kreacyjnym wzorcem projektowym, który pozwala na tworzenie obiektów bez określania ich konkretnych klas – definiowany zostaje interfejs, który umożliwia tworzenie obiektów, jednak odpowiedzialność za ten proces przekazuje do klas podrzędnych (to właśnie klasa podrzędna decyduje jaki obiekt zostanie utworzony!).

Przykład zastosowania wzorca Metoda Fabryczna

Stwórzmy aplikację, oferującą usługę wysyłania powiadomień (ostrzeżeń oraz przypomnień) za pośrednictwem poczty e-mail lub wiadomości SMS.

Bez użycia wzorca każdy z typów powiadomień tworzylibyśmy mniej więcej tak:

Notification createNotification(String serviceType, String notificationType) {
    if (serviceType.equals(“warning”)) {
        if (notificationType.equals(“sms”)) {
            return new WarningSMSNotification();
        } else if (notificationType.equals(“email”)) {
            return new WarningEmailNotification();
        } else {
            return null;
        }
    } else if (serviceType.equals(“email”)) {
        if (notificationType.equals(“sms”)) {
            return new ReminderSMSNotification();
        } else if (notificationType.equals(“email”)) {
            return new ReminderEmailNotification();
        } else {
            return null;
        }
    } else {
        return null;
    }
} 

Jak widać logika odpowiedzialna za tworzenie konkretnego powiadomienia jest skomplikowana i cechuje się wysokim stopniem złożoności. Spróbujmy zhermetyzować proces tworzenia obiektów w osobnych klasach – pomoże nam w tym wzorzec metoda fabrykująca.

Korzystając z wzorca metoda fabrykująca postępujemy zgodnie z ostatnią regułą projektowania SOLID, regułą DIP (Dependency Inversion Principle), która brzmi następująco:
Uzależniaj kod od abstrakcji a nie od klas rzeczywistych.

Wskazówki jak postępować z regułą odwracania zależności:

  • żadna zmienna nie powinna przechowywać odwołania do klas rzeczywistych,
  • żadna klasa nie powinna dziedziczyć z klasy rzeczywistej (należy dziedziczyć po elementach abstrakcyjnych),
  • żadna metoda nie powinna przesłaniać metody zaimplementowanej w dowolnej z jej klas bazowych.

Implementacja wzorca Metody Fabrykującej

Utwórzmy klasę nadrzędną dla powiadomień oraz implementacje jej klas rzeczywistych:

public abstract class Notification {
    void notifyUser() {}
    // inne metody
}

public class WarningSMSNotification extends Notification {
    @Override
    public void notify() {
        System.out.println("Sending warning SMS notification");
    }
}

public class WarningEmailNotification extends Notification {
    @Override
    public void notify() {
        System.out.println("Sending warning e-mail notification");
    }
}

public class ReminderSMSNotification extends Notification {
    @Override
    public void notify() {
        System.out.println("Sending reminder SMS notification");
    }
}

public class ReminderEmailNotification extends Notification {
    @Override
    public void notify() {
        System.out.println("Sending reminder e-mail notification");
    }
} 

Tworzymy klasę abstrakcyjną, zawierającą metodę fabrykującą, odpowiedzialną za tworzenie poszczególnych klas rzeczywistych reprezentujących powiadomienia:

public abstract class NotificationService {
    abstract Notification createNotification(String type);
    // inne metody
}

public abstract class WarningNotificationService extends NotificationService {
    Notification createNotification(String type) {
        if (type.equals(“sms”)) {
            return new WarningSMSNotification();
        } else if (type.equals(“email”)) {
            return new WarningEmailNotification();
        } else {
            return null;
        }
    }
}

public abstract class ReminderNotificationService extends NotificationService {
    Notification createNotification(String type) {
        if (type.equals(“sms”)) {
            return new ReminderSMSNotification();
        } else if (type.equals(“email”)) {
            return new ReminderEmailNotification();
        } else {
            return null;
        }
    }
} 

Wzorzec Metoda Fabrykująca w akcji

Zobaczmy działanie aplikacji z użyciem wzorca metody wytwórczej:

public abstract class NotificationTestDrive {
    public static void main(String[] args) {
        WarningNotificationService warningNotificationService = new WarningNotificationService();
        ReminderNotificationService reminderNotificationService = new ReminderNotificationService();

        Notification notification = warningNotificationService.createNotification(“sms”);
        System.out.println(notification.notify());

        notification = reminderNotificationService.createNotification(“email”);
        System.out.println(notification.notify());

    }
}
Wynik:
"Sending warning SMS notification"
"Sending reminder e-mail notification" 

Jak widzimy metoda typu Fabryka obsługuje tworzenie obiektów i powoduje hermetyzację tego procesu w klasach podrzędnych (klasach niskiego poziomu) – powoduje to usunięcie sprzężenia miedzy kodem klienta w klasie nadrzędnej i kodem w klasach podrzędnych. Spójrzmy na diagram UML wzorca.

Diagram klas UML wzorca Metody Fabrykującej

Zalety stosowania wzorca Metoda Fabrykująca

Używając wzorca metoda typu fabryka:

  • poddajemy hermetyzacji kod odpowiedzialny za proces tworzenia obiektów – zwykle jest to obszar podatny na zmiany (unikamy tym samym konieczności wprowadzania zmian w wielu miejscach aplikacji),
  • unikami duplikowania kodu a jednocześnie zapewniamy jego obsługę tylko w jednym miejscu,
  • poprzez hermetyzację logiki tworzenia obiektów usuwamy powiązanie między kodem klienta a implementacjami klas rzeczywistych.

Podsumowanie

Wzorzec Metoda Fabrykująca wykorzystuje mechanizm dziedziczenia do hermetyzacji procesu tworzenia obiektów. Jego implementacja polega na stworzeniu metody abstrakcyjnej, która zostanie zaimplementowana w klasie rzeczywistej. Jest to prosty i bardzo popularny wzorzec, z którego na pewno często będziecie korzystać.

Leave a Comment

Your email address will not be published. Required fields are marked *