Situace

Pro řešení daného problém existuje několik různých algoritmů, které mají stejné rozhraní (vstup i výstup). Klient má být od konkrétního algoritmu odstíněn, ale může ovlivnit, jaký algoritmus se použije.

Problém

Implementace různých algoritmů pro řešení jednoho problému si přímo říká o jejich zobecnění na úrovni vstupu a výstupu. Tak budou algoritmy vzájemně zaměnitelné a klient bude mít možnost zvolit takový algoritmus, který mu v danou chvíli nejlépe vyhovuje. Někdy například bude výhodnější použít pomalejší algoritmus nenáročný na paměť, jindy ten nejrychlejší možný, a to bez ohledu na použité prostředky. Odstíněním klienta od konkrétního algoritmu se zvýší vzájemná nezávislost jednotlivých součástí systému.

Řešení

Vstup a výstup algoritmů se zobecní a extrahuje do rozhraní. Toto rozhraní bude implementováno několika třídami, přičemž každá z těchto tříd představuje právě jeden algoritmus. Odstínění klienta od konkrétního algoritmu zajistí další třída, která bude uchovávat zvolený algoritmus a delegovat na něj všechny potřebné požadavky od klienta.

Varianty

  • třída s algoritmem zvolí konkrétní algoritmus sama na základě parametrů
  • konkrétní algoritmus se předává v konstruktoru
  • konkrétní algoritmus lze nastavit speciální metodou (setterem)

UML diagramy

diagram tříd (UML)

Související vzory

  • State - vnitřní stav ovlivňuje klient pouze nepřímo

Implementace

Strategie

Násobení dvou čísel lze implementovat různě. V příkladu jsou to způsoby dva: standardní násobení a postupné sčítání. Oba způsoby výpočtu implementují stejné rozhraní, protože jsou volně zaměnitelné.

package gof.strategy;

/**
 * Strategie pro násobení dvou čísel.
 * @author Vojtěch Hordějčuk
 */
public interface Strategy {
    /**
     * Vynásobí dvě čísla.
     * @param a činitel
     * @param b činitel
     * @return součin
     */
    int multiply(int a, int b);
}

Zdrojový kód Pokrytí testy

package gof.strategy;

/**
 * Standardní implementace násobení.
 * @author Vojtěch Hordějčuk
 */
public class TimesStrategy implements Strategy {
    @Override
    public int multiply(final int a, final int b) {
        return a * b;
    }
}

Zdrojový kód Pokrytí testy

package gof.strategy;

/**
 * Implementace násobení pomocí sčítání.
 * @author Vojtěch Hordějčuk
 */
public class PlusStrategy implements Strategy {
    @Override
    public int multiply(final int a, final int b) {
        int i = Math.abs(b);
        int r = a;

        while (i > 1) {
            r += a;
            i--;
        }

        return (b < 0) ? -r : r;
    }
}

Zdrojový kód Pokrytí testy

Kontext

Kontext uchovává zvolenou strategii.

package gof.strategy;

/**
 * Kontext obsahující strategii.
 * @author Vojtěch Hordějčuk
 */
public class Context {
    /**
     * aktivní strategie
     */
    private final Strategy strategy;

    /**
     * Vytvoří novou instanci.
     * @param strategy zvolená strategie
     */
    public Context(final Strategy strategy) {
        this.strategy = strategy;
    }

    /**
     * Vynásobí dvě čísla.
     * @param a činitel
     * @param b činitel
     * @return součin
     */
    public int multiply(final int a, final int b) {
        return strategy.multiply(a, b);
    }
}

Zdrojový kód Pokrytí testy

Test

Násobení dvou čísel se provede dvakrát, pokaždé s jinou strategií. V obou případech by měl být výsledek stejný, a to i přesto, že byly použité dva naprost odlišné algoritmy. Klient nemusí vědět o tom, jak algoritmy pracují.

package gof.strategy;

import org.junit.Test;

public class Example {
    @Test
    public void test() {
        // test první strategie

        final Context context1 = new Context(new TimesStrategy());
        final int r1 = context1.multiply(3, 5);
        System.out.println(r1); // 3*5 = 15

        // test druhé strategie

        final Context context2 = new Context(new PlusStrategy());
        final int r2 = context2.multiply(3, 5);
        System.out.println(r2); // 3*5 = 3+3+3+3+3 = 15
    }
}

Zdrojový kód Pokrytí testy

Reference