JE_22: Nahrazujte ukazatele na funkce třídami a rozhraními

Jazyk C podporuje ukazatele na funkce, které umožňují programu uložit si a přenést schopnost zavolat určitou funkci. Ukazatele na funkce se typicky používají k tomu, aby umožnily volajícímu funkce specialzovat její chování předáním ukazatele na jinou funkci, xož se někdy označuje za zpětné volání. Například funkce qsort ve standardní knihovně jazyka C přebírá ukazatel na porovnávací funkci (komparátor), kterou používá k porovnávání řazených prvků. Porovnávací funkce přebírá dva parametry, přičemž oba jsou ukazateli na nějaký prvek. Vrací záporné celé číslo, je-li prvek odkazovaný prvním parametrem menší než prvek odkazovaný druhým parametrem, nulu, když jsou si oba prvky rovné, a kladné celé číslo, když je prvek odkazovaný prvním parametrem větší než prvek odkazovaný druhým parametrem. Různé pořadí lze získat předáním různých porovnávacích funkcí. Tento příklad je ukázkou vzoru strategie [Gamma95, str. 135]; porovnávací funkce představuje určitou strategii řazení prvků.

Ukazatele na funkce byly z programovacího jazyka Java vypušteny, protože k zajištění stejné funkčnosti lze použít odkazy na objekty. Volání metody v nějakém objektu obvykle vykoná určitou operaci s daným objektem. He však také možné definovat objekt, jehož metody vykonávají operace na jiných objektech, ktereé jsou těmto metodám explicitně předány. Instance nějaké třídy, jež exportuje přesně jednu takovou metodu, je vlastně ukazatelem na danou metodu. Takové instance se označují za funkční objekty. Zvažme například následující třídu:

class StringLengthComparator {
	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
}

Tato třída exportuje jedinou metodu přebírající dva řetězce a vracející záporné celé číslo, je-li první řetězec kratší než druhý, nulu, jsou-li oba řetězce stejně dlouhé a kladné celé číslo je-li první řetězec delší. Tato metoda je komparátorem, který řadí řetězce podle jejich délky namísto typičtějšího abecedního řazení. Odkaz na nějaký objekt StringLengthComparator slouží jako "ukazatel na funkci" tohoto komparátoru a umožňuje jej volat s libovolnými dvojicemi řetězců. Jinými slovy, instance StringLengthComparator je konkrétní strategií pro porovnávání řetězců.

Jak je už pro třídy konkrétní strategie typické, třída StringLengthComparator je nestavová: nemá žádné atributy, takže jsou všechny instance dané třídy funkčně ekvivalentní. Může se stejně dobře jednat o jedináčka, čímž si ušetříte zbytečné náklady na vytváření objektů (rada 2, rada 04):

class StringLengthComparator {
	private StringLengthComparator() {
	}

	public static final StringLengthComparator INSTANCE = new StringLengthComparator();

	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
}

Abychommohli předat instanci StringLengthComparator nějaké metodě, potřebujeme vhodný typ daného parametru. Nebylo by dobré používat StringLengthComparator, protože klienti by nemohli předat žádnou jinou porovnávací strategii. Místo toho musíme definovat rozhraní Comparator a upravit třídu StringLengthComparator tak, aby implementovala toto rozhraní. Jinými slovy, musíme nadefinovat rozhraní strategie příslušné konkrétní strategické třídě. Zde je máme:

// Rozhraní strategie
public interface Comparator {
	public int compare(Object o1, Object o2);
}

Tato definice rozhraní Comparator náhodou pochází z balíčku java.util, ale není na nic záhadného; stejně tak jste si ji mohli nadefinovat sami. Aby ji bylo možné používat také v komparátorech objektů a nejen řetězců, její metoda compare přebírá parametry typu Object a nikoli typu String. Proto je zapotřebí výše uvedenou třídu StringLengthComparator trochu změnit, aby implementovala Comparator: parametry Object je zapotřebí před voláním metody length převést na typ String.

Konkrétní strategické třídy se často deklarují prostřednictvím anonymních tříd (rada 18). Následujíc příkaz řadí pole řetězců podle délky:

	Arrays.sort(stringArray, new Comparator() {
		public int compare(Object o1, Object o2) {
			String s1 = (String)o1;
			String s2 = (String)o2;
			return s1.length() - s2.length();
		}
	});

Protože rozhraní strategie slouží jako typ všem svým konkrétním instancím strategií, není zapotřebí činit konkrétní strategickou třídu veřejnou, aby mohla exportovat konkrétní strategii. Místo toho může "hostitelská třída" exportovat nějaký veřejný statický atribut (nebo statickou tovární metodu), jehož typem je dané strategické rozhraní, a daná konkrétní strategická třída může být soukromou vnořenou třídou tohoto hostitele. V následujícím oříkladu se dává přednost statické členské třídě před anonymní třídou, aby mohla konkrétní strategická třída implementovat druhé rozhraní, Serializable:

// Exportování konkrétní strategie
class Host {
	. . . // Kód vypuštěn

	private static class StrLenCmp implements Comparator, Serializable {
		public int compare(Object o1, Object o2) {
			String s1 = (String)o1;
			String s2 = (String)o2;
			return s1.length() - s2.length();
		}
	}

	// Vrácený komparátor je serializovatelný
	public static final Comparator STRING_LENGTH_COMPARATOR = new StrLenCmp();
}

Třída String používá tento vzor k exportování komparátoru, který nerozlišuje velikost písmen, prostřednictvím svého atributu CASE_INSENSITIVE_ORDER.

Závěrem lze říci, že hlavním využitím ukazatelů na funkce jazyka C je implementování strategického vzoru. Chcete-li implementovat tento vzor v programovacím jazyku Java, deklarujte nějaké rozhraní představující danou strategii a třídu implementující toto rozhraní pro každou konkrétní strategii. Je-li nějaké konkrétní strategie používána jen jednou, její třída se typicky deklaruje a její instance se vytváří prostřednictvím anonymní třídy. Je-li nějaká konkrétní strategie exportována k opakovanému používání, její třída je obvykle soukromou statickou členskou třídou, jež je exportována prostřednictvím nějakého veřejného statického finálního atributu, jehož typem je strategické rozhraní.