Při objektově orientovaném programování často dochází k nutnosti řešit programátorské postupy, které můžeme označit jako Best practise, tedy takové postupy, které jsou lety prověřeny a které zajistí jak lepší čitelnost kódu, tak i to, že po nějaké době nebudeme muset svůj kód přepisovat z důvodu chyb v návrhu. Jedná se o návrhové vzory (design patterns).

Existuje velké množství návrhových vzorů a samozřejmě vznikají stále další. Naopak některé postupy sice stále platí, ale vlivem používání různých technik, např. Dependency injection, se již nepoužívají (např. sigleton).

Smyslem tohoto článku je napsat úvod do návrhových vzorů, které na serveru algoritmy.net leží již nějakou dobu, ale nejsou sjednoceny a vysvětleny v celku. Postupně budeme články o návrhových vzorech na serveru rozšiřovat a upravovat tak, aby odpovídali modernější verzi Java 7 a Java 8, povíme si o tom, jak by vypadala implementace i v jiných programovacích jazycích, například v PHP, nebo za použití frameworku Spring.

Ukázka smyslu návrhových vzorů:

Představte si, že programátor tvoří hru, ve které figuruje několik druhů kachen. Tyto kachny se vykreslují, mají společné vlastnosti a plavou po obrazovce. Chytrý programátor navrhne tedy třídu Kachna, ze které budou ostatní kachny děděny, využije tedy základní princip Objektově Orientovaného Programování a tím je dědičnost. Kachna přeci plave, kváká a nějak se zobrazuje, tak proč toho nevyužít. Všechny tyto tři metody tedy každá instance kachny dědí. Maximálně přepíšeme u každé kachny její zobrazení, přeci jen je každá kachna jiná.

třída Kachna

Problém nastane v okamžiku, kdy chceme, aby každá kachna také létala. Programátor si řekne že to není problém, všechny kachny létají a tak přidá metodu létání() do třídy Kachna.

ke třídě Kachna přidána metoda létání()

Po měsíci se však přijde na to, že po přidání instance GumováKachnička tato létá po obrazovce. To však nikdo nechtěl. Programátor chtěl pouze znovupoužitelný kód. A pak se začaly vynořovat problémy s přidáním dalších typů kachen, okrasných kachen, kachních návnad na lišky a problém nabobtnával.

Programátor tedy začal uvažovat o rozhraní (interface). Odstranil z třídy Kachna metodu létání() a navrhl rozhraní schopnostLétat() a schopnostKvákat(), kterou budou budou implementovat pouze kachny, které mají schopnost létat.

vyjmutí do interface

To tedy znamená, že ten svůj program celý přepíše? Ano. Lépe teď, než později, kdy už nabobtná do takových rozměrů, že to nepůjde.

První pravidlo návrhu dobrého kódu tedy zní:
Identifikujte části kódu, které se budou měnit a oddělte je od kódu, který zůstává stejný.
Další pravidlo návrhu dobrého kódu zní:
Programujte proti rozhraní, nikoliv proti implementaci.

Programování proti rozhraní snižuje závislost na implementaci a dělá tak kód více znovu-použitelný, tvůrce programu může později měnit chování programu, tak že zamění stávající objekt za nový, který však implementuje stejné rozhraní.

Kód

//Definujeme dvě rozhraní
public interface SchopnostKvakani {
	kvakani();
}

public interface SchopnostLetani {
	letani();
}

//Implementujeme potřebné létání a kvákání
public class LetaniPomociKridel implements SchopnostLetani {
	letani() { //Implementace letani pomoci kridel
	}
}

public class LetaniBezKridel implements SchopnostLetani {
	letani() { //Implementace letani bez kridel
	}
}

public class Piskani implements SchopnostKvakani {
	kvakani() { //Implementace piskani 
	}
}

public class ZadnyZvuk implements SchopnostKvakani {
	kvakani() { // nevydávej zvuk
		}
	}


public abstract class Kachna implements SchopnostKvakani, SchopnostLetani {
	SchopnostLetani schopnostLetani;
	SchopnostKvakani schopnostKvakani;
	// více
	
	public void kvakej() {
		schopnostKvakani.kvakani();
	}
	
	public void letej() {
		schopnostLetani.letani();
	}
}

public class DivokaKachna extends Kachna {
	SchopnostLetani schopnostLetani;
	SchopnostKvakani schopnostKvakani;
	
	public DivokaKachna() {
		schopnostKvakani = new ZadnyZvuk(); //Ale může také pískat
		schopnostLetani = new LetaniPomociKridel(); //Také nemusí létat
	}
}

public class MiniSimulatorKachen {
	public static void main(String[] args){
		Kachna divoka = new DivokaKachna();
		divoka.kvakej; //Zde voláme děděnou metodu Divoké kachny, která se implementuje schopnostKvakani
		divoka.letej; // a schopnost létání
	}
}

Co když ale půjdeme ještě dál. Dá se měnit chování kachny za běhu programu? Samozřejmě že dá. A pomůže k tomu právě programování proti rozhraní.

Programování proti rozhraní

Kód

//Upravíme tedy třídu Kachna, přidáme settery k nastavení schopností za běhu programu.
public abstract class Kachna implements SchopnostKvakani, SchopnostLetani {
	SchopnostLetani schopnostLetani;
	SchopnostKvakani schopnostKvakani;
	// více
	
	public void kvakej() {
		schopnostKvakani.kvakani();
	}
	
	public void letej() {
		schopnostLetani.letani();
	}
	
	public void setSchopnostLetani(SchopnostLetani schLet) {
		this.schopnostLetani = schLet;
	}
	
	public void setSchopnostKvakani(SchopnostKvakani schKva) {
		this.schopnostKvakani = schKva;
	}
	
}

//Vytvoříme novou (dynamickou) kachnu
public class GumovaKachnicka {
	public GumovaKachnicka() {
		schopnostLetani = new LetaniBezKridel();
		schopnostKvakani = new Piskani();
	}
}

//A novou možnost létat pomocí raketového motoru
public class LetaniPomociRaketovehoMotoru implements SchopnostLetani {
	public void letani() { //Implementace letani s raketovým motorem
	}
}

//A takto za běhu programu měníme schopnost létání
public class MiniSimulatorKachen {
	public static void main(String[] args){
		Kachna divoka = new DivokaKachna();
		divoka.kvakej; //Zde voláme děděnou metodu Divoké kachny, která se implementuje schopnostKvakani
		divoka.letej; // a schopnost létání
		
		Kachna gumova = new GumovaKachnicka();
		gumova.letej(); //Létá bez křídel
		gumova.setSchopnostLetani(new LetaniPomociRaketovehoMotoru);
		gumova.letej(); //A bude létat pomocí raketového pohonu
		
	}
}

No a tento postup je návrhovým vzorem Strategie. Zde je pak stručnější popis návrhového vzoru Strategie

Další námi uváděné návrhové vzory jsou:

Ze skupiny Gang Of Four:

Návrhový vzor Template method
Návrhový vzor Strategie
Návrhový vzor Visitor
Návrhový vzor Iterator
Návrhový vzor Singleton
Návrhový vzor Abstract Factory
Návrhový vzor Object Pool
Návrhový vzor Decorator
Návrhový vzor Adapter

A další návrhové vzory:

Návrhový vzor Multiton
Návrhový vzor Simple factory method
Návrhový vzor Null Object
Návrhový vzor Library class

Zdroj








Doporučujeme