JE_1 - Zvažte poskytnutí statických továrních metod namísto konstruktorů

Když má třída umožnit klientovi získat nějakou instanci, obvykle mu poskytuje určitý veřejný konstruktor. Existuje rovněž další, méně známá technika, která by měla být součástí výbavy každého programátora. Třída může poskytovat nějakou veřejnou statickou tovární metodu, což je prostě statická metoda, která vrací instanci dané třídy. Zde máme jednoduchý příklad ze třídy Boolean (obalové třídy primitivního typu boolean). Tato statická tovární metoda, která byla přidána ve verzi 1.4, překládá primitivní hodnotu boolean na odkaz na objekt Boolean:

public static Boolean valueOf(boolean b) {
	return (b ? Boolean.TRUE : Boolean.FALSE);
}

Třída může svým klientům poskytovat statické tovární metody namísto (nebo jako doplněk) konstruktorů. Poskytnutí statické tovární metody namísto veřejného konstruktoru má své výhody i nevýhody.

První výhodou statických továrních metod je to, že na rozdíl od konstruktorů mají názvy. Pokud parametry konstruktoru samy o sobě nepopisují vracený objekt, pak může být snažší používat nějakou statickou tovární metodu s dobře zvoleným názvem - klientský kód bude čitelnější. Například konstruktor BigInteger(int, int, Random), který vrací číslo BigInteger, jež je pravděpodobně prvočíslem, by byl lépe vyjádřen jako statická tovární metoda nazvaná BigInteger.probablePrime (tato statická tovární metoda byla ve skutečnosti přidána do verze 1.4).

Třída může mít jen jediný konstruktor s daným podpisem. Je známo, že toto omezení obcházejí programátoři tak, že vytvářejí dva konstruktory, jejichž seznamy parametrů se liší pouze pořadím typů parametrů. To je ale špatné. Uživatel takového API si nikdy nezapamatuje, který konstruktor je který a nakonec bude chybně volat ten nesprávný. Lidé, kteří budou číst kód používající tyto konstruktory, nebudou vědět co kód dělá, dokud se nepodívají do dokumentace dané třídy.

Protože statické tovární metody mají názvy, nesdílejí s konstruktory omezení spočívající v tom, že třída může mít jen jeden konstruktor s daným podpisem. V případech, kdy je alepso� zdánlivě zapotřebí mít ve třídě více konstruktorů se stejnými podpisy, zvažte nahrazení jednoho nebo více konstruktorů statickými továrními metodami, jejichž pečlivě vybrané názvy zdůrazní vzájemné rozdíly.

Druhou výhodou statických továrních metod je to, že narozdíl od konstruktorů nemusejí vytvářet nový objekt při každém volání. To umož�uje neměnitelným třídám (rada 13) používat předem zkonstruované instance nebo si instance ukládat do mezipaměti (cache) během jejich vytváření a tyto instance opakovaně rozdělovat a vyhýbat se tak vytváření zbytečných duplicitních objektů rada 4). Tuto techniku ilustruje například metoda Boolean.valueOf(boolean): nikdy nevytvářet objekt. Tato technika může výrazně zlepšit výkonnost v situaci, kdy jsou často požadovány ekvivalentní objekty, zejména je-li vytváření těchto objektů náročné.

Schopnost staických metod vracet tentýž objekt při opakovaných voláních lze rovněž využít k zavedení striktního řízení instancí existujících v libovolném okamžiku. Existují dva důvody, proč je to výhodné. Za prvé to umož�uje třídě zaručit, že je jedináčkem (rada 2 ). Za druhé to umož�uje neměnitelné třídě zaručit, že nebudou existovat žádné dvě rovné instance: a.equals(b) jen a pouze když a == b. Pokud to nějaká třída zaručí, pak mohou její klienti používat operátor == místo metody equals(Object), což může mít za následek značné zvýšení výkonnosti. Vzor typově zabezpečeného výčtu popsaný v radě21 implementuje tuto optimalizaci a také metoda String.intern ji implementuje v omezené formě.

Třetí výhodou statických továrních metod je to, že narozdíl od konstruktorů mohou vracet objekt libovolného podtypu svého návratového typu. To vám umož�uje velmi pružně volit třídu vraceného objektu.

Jednou z aplikací této pružnosti je to, že API může vracet objekty, aniž by zveřej�ovalo jejich třídy. Skrývání implementačních tříd tímto způsobem může vést k velmi kompaktnímu API. Tato technika se využívá u rámců založených na rozhraní, kde rozhraní poskytují přirozené návratové typy pro statické tovární metody.

Například rámec kolekcí (Collections Framework) má dvacet pomocných implementací svých rozhraní kolekcí, které poskytují neupravitelné kolekce, synchronizované kolekce apod. Velká většina těchto implementací se exportuje prostřednictvím statických továrních metod v jediné třídě, jejíž instance nelze vytvářet (java.util.Collections). Třídy vracených objektů jsou všechny neveřejné.

API rámce kolekcí je mnohem menší, než kdyby exportovalo dvacet samostatných veřejných tříd pomocných implementací. Neomezuje se však pouze na rozměr celého API, ale také "koncepční hmotnost". Uživatel ví, že vracený objekt má přesně to API specifikované odpovídjícím rozhraním, takže nemusí číst další dokumentaci třídy. Navíc použití takové statické tovární metody zajistí, že se bude klient odkazovat na vrácený objekt jeho rozhraním a nikoli jeho implementační třídou, což je obecně dobrý přístup (rada 34).

Nejenže může být třída objektu vráceného veřejnou statickou tovární metodou neveřejná, ale tato třída se může při různých voláních lišit podle hodnot parametrů statické tovární metody. K dispozici je každá třída, která je podtypem deklarovaného návratového typu. Třída vráceného objektu se také může lišit v jednotlivých verzích, čímž se zvyšuje spravovatelnost softwaru.

Třída objektu vráceného statickou tovární metodou dokonce ani nemusí existovat v okamžiku vytváření třídy obsahující danou statickou tovární metodu. Takové flexibilní statické tovární metody tvoří základ rámců poskytovatelů služeb (Service Provider Frameworks), jako je kryptografické rozhraní Javy (JCE - Java Cryptography Extension). Rámec poskytovatelů služeb je systém, v němž poskytovatelé vytvářejí více implementací určitého API a poskytují je uživatelům. Existuje mechanismus registrování těchto implementací, který umož�uje jejich používání. Klienti daného rámce používají toto API a nestarají se o to, se kterou implementací pracují.

V JCE si správce systému registruje implementační třídu úpravou dobře známého souboru Properties, kdy zadá položku přiřazující nějaký řetězcový klíč odpovídajícímu názvu třídy. Klienti používají statickou tovární metodu, která jako parametr přebírá daný klíč. Daná statická tovární metoda vyhledá objekt Class v mapě inicializované souborem Properties a vytvoří instanci této třídy pomocí metody Class.newInstance. Následující implementační ukázka ilustruje tuto techniku:

//Ukázka rámce poskytovatele
public abstract class Foo {
	//Přiřazuje řetězec String odpovídajícímu objektu Class
	private static Map implementations = null;

	//Inicializuje mapu implementací při prvním volání
	private static synchronized void initMapIfNecessary() {
		if (implementations == null) {
			implementations == new HashMap();

			//Nahrát názvy implementační třídy a klíče ze
			//souboru Properties, přeložit názvy na objekty
			//Class pomocí Class.forName a uložit přpřazení
			...
		}
	}

	public static Foo getInstance(String key) {
		initMapIfNecessary();
		Class c = (Class)implementations.get(key);
		if (c == null)
			return new DefaultFoo();
		try {
			return (Foo)c.newInstance();
		} catch (Exception e) {
			return new DefaultFoo();
		}
	}
}

Hlavní nevýhodou statických továrních metod je to, že třídy bez veřejných nebo chráněných konstruktorů nelze uspořádávat do hierarchií. Totéž platí pro neveřejné třídy vracené statickými továrnami. Není napříkad možné vytvářet podtřídy (dceřinné třídy) z žádné z pomocných implementačních tříd v API rámce kolekcí. Tuto skutečnost lze také ale považovat za skrýté požehnání, protože nutí programátory používat kompozici namísto dědičnosti (rada 14).

Druhou nevýhodou statických továrních metod je to, že je nelze snadno odlišit od jiných statických metod. Nevyčnívají v dokumentaci API takovým způsobem, jako to činí konstruktory. Navíc představují statické tovární metody odchylku od normy. Proto může být obtížné zjistit z dokumentace třídy, jak vytvořit instanci nějaké třídy, která poskytuje statické tovární metody namísto konstruktorů. Tuto nevýhodu lze omezit dodržováním standardních konvencí vytváření názvů. Tyto konvence se ještě vyvíjí, stále častěji se však objevují dva názvy statických továrních metod:

Celkově lze tedy říci, že jak statické tovární metody, tak i veřejné konstruktory mají svá využití a vyplatí se chápat jejich relativní přínosy. Vyhýbejte se reflexivnímu poskytování konstruktorů, aniž byste zvážili statické továrny, protože statické továrny jsou často vhodnější. Pokud jste zvážili obě možnosti a nic vás silněji nepostrčilo žádným směrem, pak je pravděpodobně lepší poskytnout konstruktor prostě proto, že je to běžné.