Przeznaczenie

Należy do behawioralnych wzorców projektowych. Pozwala zdefiniować konkretne zachowanie/algorytm i wydzielić je do osobnych klas. Następnie umozliwia wymienne stosowanie obiektów tych klas podczas działania programu.

Dlaczego go potrzebujemy?

Powiedzmy, że chcemy wyciąć kilka drzewek na swoim ogrodzie i wynajmujemy w tym celu drwala.

public class Lumberjack {
    public void cutTree(){
        System.out.println("Ścinam drzewo siekierą.");
    }
}
 

Klasa reprezentujaca drwala ścinającego drzewo siekierą.

public class Main {
    public static void main(String[] args) {
        Lumberjack lumberjack = new Lumberjack();
        lumberjack.cutTree();
    }
} 

Ścięcie drzewa przez drwala.

Ścinam drzewo siekierą. 

Output

Problem zaczyna się gdy chcielibyśmy, żeby z jakiegoś względu drwal zmienił narzędzie i w tym przypadku z pomocą przychodzi nam wzorzec strategia.

Wykorzystanie

Celem tego wzorca jest rozwiązanie tego samego zadania na różne sposoby, przy czym osobę zlecającą dane zadanie nie interesuje jak ono jest zrobione, po prostu ma zostać zrobione na wybrany sposób, np. drzewo ma zostać ścięte piłą.

Struktura

Implementacja

Aby zaimplementować ten wzorzec musimy zmienić klasę Luberjack, a także stworzyć package, w którym będziemy trzymać interfejs naszej strategii oraz jego implementację. Zacznijmy od interfejsu.

package cuttingTrees;

public interface CuttingTreesStrategy {
    void cut();
}
 

Interfejs sposobów ścinania drzewa.

package cuttingTrees;

public class AxeStrategy implements CuttingTreesStrategy{
    @Override
    public void cut() {
        System.out.println("Ścinam drzewo siekierą.");
    }
}
 

Implementacja klasy do ścianania drzew siekierą.

package cuttingTrees;

public class SawStrategy implements CuttingTreesStrategy{
    @Override
    public void cut() {
        System.out.println("Ścinam drzewo piłą.");
    }
}
 

Implementacja klasy do ścianania drzew piłą.

package cuttingTrees;

public class ChainsawStrategy implements CuttingTreesStrategy{
    @Override
    public void cut() {
        System.out.println("Ścinam drzewo piłą mechaniczną.");
    }
}
 

Implementacja klasy do ścianania drzew piłą mechaniczną.

import cuttingTrees.CuttingTreesStrategy;

public class Lumberjack {
    private CuttingTreesStrategy cuttingTreesStrategy;

    public Lumberjack(CuttingTreesStrategy cuttingTreesStrategy) {
        this.cuttingTreesStrategy = cuttingTreesStrategy;
    }

    public void setCuttingTreesStrategy(CuttingTreesStrategy cuttingTreesStrategy) {
        this.cuttingTreesStrategy = cuttingTreesStrategy;
    }

    public void cutTree(){
        cuttingTreesStrategy.cut();
    }
}
 

Nowa klasa drwala. Możemy w niej ustawiać strategię dotyczącą sposobu ścianania.

import cuttingTrees.AxeStrategy;
import cuttingTrees.ChainsawStrategy;
import cuttingTrees.SawStrategy;

public class Main {
    public static void main(String[] args) {
        Lumberjack lumberjack = new Lumberjack(new SawStrategy());
        lumberjack.cutTree();

        lumberjack.setCuttingTreesStrategy(new ChainsawStrategy());
        lumberjack.cutTree();

        lumberjack.setCuttingTreesStrategy(new AxeStrategy());
        lumberjack.cutTree();
    }
} 

Wywołanie programu.

Ścinam drzewo piłą.
Ścinam drzewo piłą mechaniczną.
Ścinam drzewo siekierą. 

Output

Podsumowanie

Zalety

  • możliwość zmiany algorytmy podczas działania programu
  • izolacja szczegółów implementacyjnych algorytmu od kodu, który z niego korzysta
  • używanie kompozycji, a nie dziedziczenia
  • możliwość dodawania nowych strategii, bez konieczności dodawania zmian w kontekście

Wady

  • dodatkowy koszt komunikacji między klientem, a strategią
  • klient musi znać różnicę pomiędzy poszczególnymi strategiami, aby móc wybrać właściwą