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á.
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.
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.
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
01.
//Definujeme dvě rozhraní
02.
public
interface
SchopnostKvakani {
03.
kvakani();
04.
}
05.
06.
public
interface
SchopnostLetani {
07.
letani();
08.
}
09.
10.
//Implementujeme potřebné létání a kvákání
11.
public
class
LetaniPomociKridel
implements
SchopnostLetani {
12.
letani() {
//Implementace letani pomoci kridel
13.
}
14.
}
15.
16.
public
class
LetaniBezKridel
implements
SchopnostLetani {
17.
letani() {
//Implementace letani bez kridel
18.
}
19.
}
20.
21.
public
class
Piskani
implements
SchopnostKvakani {
22.
kvakani() {
//Implementace piskani
23.
}
24.
}
25.
26.
public
class
ZadnyZvuk
implements
SchopnostKvakani {
27.
kvakani() {
// nevydávej zvuk
28.
}
29.
}
30.
31.
32.
public
abstract
class
Kachna
implements
SchopnostKvakani, SchopnostLetani {
33.
SchopnostLetani schopnostLetani;
34.
SchopnostKvakani schopnostKvakani;
35.
// více
36.
37.
public
void
kvakej() {
38.
schopnostKvakani.kvakani();
39.
}
40.
41.
public
void
letej() {
42.
schopnostLetani.letani();
43.
}
44.
}
45.
46.
public
class
DivokaKachna
extends
Kachna {
47.
SchopnostLetani schopnostLetani;
48.
SchopnostKvakani schopnostKvakani;
49.
50.
public
DivokaKachna() {
51.
schopnostKvakani =
new
ZadnyZvuk();
//Ale může také pískat
52.
schopnostLetani =
new
LetaniPomociKridel();
//Také nemusí létat
53.
}
54.
}
55.
56.
public
class
MiniSimulatorKachen {
57.
public
static
void
main(String[] args){
58.
Kachna divoka =
new
DivokaKachna();
59.
divoka.kvakej;
//Zde voláme děděnou metodu Divoké kachny, která se implementuje schopnostKvakani
60.
divoka.letej;
// a schopnost létání
61.
}
62.
}
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í.
Kód
01.
//Upravíme tedy třídu Kachna, přidáme settery k nastavení schopností za běhu programu.
02.
public
abstract
class
Kachna
implements
SchopnostKvakani, SchopnostLetani {
03.
SchopnostLetani schopnostLetani;
04.
SchopnostKvakani schopnostKvakani;
05.
// více
06.
07.
public
void
kvakej() {
08.
schopnostKvakani.kvakani();
09.
}
10.
11.
public
void
letej() {
12.
schopnostLetani.letani();
13.
}
14.
15.
public
void
setSchopnostLetani(SchopnostLetani schLet) {
16.
this
.schopnostLetani = schLet;
17.
}
18.
19.
public
void
setSchopnostKvakani(SchopnostKvakani schKva) {
20.
this
.schopnostKvakani = schKva;
21.
}
22.
23.
}
24.
25.
//Vytvoříme novou (dynamickou) kachnu
26.
public
class
GumovaKachnicka {
27.
public
GumovaKachnicka() {
28.
schopnostLetani =
new
LetaniBezKridel();
29.
schopnostKvakani =
new
Piskani();
30.
}
31.
}
32.
33.
//A novou možnost létat pomocí raketového motoru
34.
public
class
LetaniPomociRaketovehoMotoru
implements
SchopnostLetani {
35.
public
void
letani() {
//Implementace letani s raketovým motorem
36.
}
37.
}
38.
39.
//A takto za běhu programu měníme schopnost létání
40.
public
class
MiniSimulatorKachen {
41.
public
static
void
main(String[] args){
42.
Kachna divoka =
new
DivokaKachna();
43.
divoka.kvakej;
//Zde voláme děděnou metodu Divoké kachny, která se implementuje schopnostKvakani
44.
divoka.letej;
// a schopnost létání
45.
46.
Kachna gumova =
new
GumovaKachnicka();
47.
gumova.letej();
//Létá bez křídel
48.
gumova.setSchopnostLetani(
new
LetaniPomociRaketovehoMotoru);
49.
gumova.letej();
//A bude létat pomocí raketového pohonu
50.
51.
}
52.
}
Další námi uváděné návrhové vzory jsou:
Ze skupiny Gang Of Four:
Návrhový vzor Template methodNá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 MultitonNávrhový vzor Simple factory method
Návrhový vzor Null Object
Návrhový vzor Library class
Zdroj
- Head First Design Patterns, Eric Freeman a Elisabeth Freeman, OREILLY publishing.