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
//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í.
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 } }
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.