návrhový vzor Singleton.



Podobné dokumenty
Využití OOP v praxi -- Knihovna PHP -- Interval.cz

Statické proměnné a metody. Tomáš Pitner, upravil Marek Šabo

OMO. 4 - Creational design patterns A. Singleton Simple Factory Factory Method Abstract Factory Prototype Builder IoC

Objektově orientované programování v PHP 5. Martin Klíma

Objekty v PHP 5.x. This is an object-oriented system. If we change anything, the users object.

typová konverze typová inference

1. Programování proti rozhraní

Základy objektové orientace I. Únor 2010

Bridge. Známý jako. Účel. Použitelnost. Handle/Body

Více o konstruktorech a destruktorech

Objektové programování

Marian Böhmer. Návrhové vzory v PHP

PREPROCESOR POKRAČOVÁNÍ

State. Známý jako. Účel. Použitelnost. Stav, Object for States. umožňuje objektu měnit svoje chování v závislosti na stavu objekt mění svou třídu

1. Dědičnost a polymorfismus

Kód, který se nebude často měnit

Návrhové vzory. Jakub Klemsa, Jan Legerský. 30. října Objektově orientované programování.

Pokročilé programování v jazyce C pro chemiky (C3220) Operátory new a delete, virtuální metody

20. Projekt Domácí mediotéka

IRAE 07/08 Přednáška č. 2. atr1 atr2. atr1 atr2 -33

Programování v C++ 2, 4. cvičení

Semin aˇr Java N avrhov e vzory Radek Ko ˇc ı Fakulta informaˇcn ıch technologi ı VUT Duben 2009 Radek Koˇc ı Semin aˇr Java N avrhov e vzory 1/ 25

Seminář Java IV p.1/38

Dalším příkladem může být například výstup dat na různá zařízení, souborů, grafických rozhraní, sítě atd.

Výčtový typ strana 67

Návrhové vzory Design Patterns

ČÁST 1. Zahřívací kolo. Co je a k čemu je návrhový vzor 33

PŘETĚŽOVÁNÍ OPERÁTORŮ

Pokročilé schopnosti OOP

Konstruktory a destruktory

NSWI096 - INTERNET JavaScript

Z. Kotala, P. Toman: Java ( Obsah )

Generické programování

Třídy a objekty. Třídy a objekty. Vytvoření instance třídy. Přístup k atributům a metodám objektu. $z = new Zlomek(3, 5);

Semin aˇr Java N avrhov e vzory Radek Ko ˇc ı Fakulta informaˇcn ıch technologi ı VUT Duben 2008 Radek Koˇc ı Semin aˇr Java N avrhov e vzory 1/ 24

DUM 06 téma: Tvorba makra pomocí VBA

Polymorfismus. Časová náročnost lekce: 3 hodiny Datum ukončení a splnění lekce: 30.března

Úvod do programovacích jazyků (Java)

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

3. Třídy. Základní pojmy objektového programování. Třídy

Pokročilé programování v jazyce C pro chemiky (C3220) Statické proměnné a metody, šablony v C++

Funkční objekty v C++.

Quo vadis programování? Automatizace vyhodnocování studentských úloh

Definice třídy. úplná definice. public veřejná třída abstract nesmí být vytvářeny instance final nelze vytvářet potomky

Programování II. Návrh programu I 2018/19

, Brno Připravil: David Procházka Návrhové vzory

Úvod Třídy Rozhraní Pole Konec. Programování v C# Hodnotové datové typy, řídící struktury. Petr Vaněček 1 / 39

Dědičnost (inheritance)

Michal Krátký. Úvod do programovacích jazyků (Java), 2006/2007

TŘÍDY POKRAČOVÁNÍ. Události pokračování. Příklad. public delegate void ZmenaSouradnicEventHandler (object sender, EventArgs e);

Jazyk C++ 1. Blok 3 Objektové typy jazyka C++ Třída. Studijní cíl. Doba nutná k nastudování. Průvodce studiem

Abstract Factory úvod

Java - řazení objektů

Programování v C++ 3, 3. cvičení

přetížení operátorů (o)

Jazyk C++ I. Šablony 2

11 Diagram tříd, asociace, dědičnost, abstraktní třídy

TÉMATICKÝ OKRUH Softwarové inženýrství

Úvod - problém. Při přidání nového modelu je nutné upravit. Kód, který se nebu de často měnit. n Mějme obchod s auty:

7. přednáška - třídy, objekty třídy objekty atributy tříd metody tříd

Programování v Javě I. Únor 2009

Programování v C++ 1, 6. cvičení

PB161 Základy OOP. Tomáš Brukner

Mělká a hluboká kopie

Programování v Javě I. Leden 2008

Pokročilé programování v jazyce C pro chemiky (C3220) Třídy v C++

specifikuje vytvářené objekty pomocí prototypické instance nový objekt vytváří kopírováním prototypu

Objektově orientované programování v jazyce Python

PB161 Programování v jazyce C++ Přednáška 7

Obsah přednášky 9. Skrývání informací. Skrývání informací. Zapouzdření. Skrývání informací. Základy programování (IZAPR, IZKPR) Přednáška 9

Jazyk C++ I. Polymorfismus

Jazyk C# (seminář 6)

PB161 Programování v jazyce C++ Přednáška 7

2) Napište algoritmus pro vložení položky na konec dvousměrného seznamu. 3) Napište algoritmus pro vyhledání položky v binárním stromu.

Návrhové vzory OMO, LS 2014/2015

Viditelnost (práva přístupu) Tomáš Pitner, upravil Marek Šabo

Programování v jazyce JavaScript

<surface name="pozadi" file="obrazky/pozadi/pozadi.png"/> ****************************************************************************

10 Generické implementace

Dynamicky vázané metody. Pozdní vazba, virtuální metody

IRAE 07/08 Přednáška č. 1

Skripta ke školení. Základy VBA. vypracoval: Tomáš Herout. tel:

Seznamy a iterátory. Kolekce obecně. Rozhraní kolekce. Procházení kolekcí

Obsah přednášky 7. Základy programování (IZAPR) Přednáška 7. Parametry metod. Parametry, argumenty. Parametry metod.

Struktura třídy, operátory, jednoduché algoritmy, junit. Programování II 2. cvičení Alena Buchalcevová

Pokročilé programování v jazyce C pro chemiky (C3220) Pokročilá témata jazyka C++

Objektově orientované programování v jazyce Python

Mnohotvarost (polymorfizmus)

8 Třídy, objekty, metody, předávání argumentů metod

Výchozí a statické metody rozhraní. Tomáš Pitner, upravil Marek Šabo

IRAE 07/08 Přednáška č. 7. Začátek (head)

Objektově orientované technologie Diagram komponent Implementační náhled (Diagram rozmístění) Pavel Děrgel, Daniela Szturcová

Třída DrawingTool. Obrázek 1: Prázdné okno připravené pro kreslení

IUJCE Přednáška č. 11. další prvky globální proměnné, řízení viditelnosti proměnných, funkcí

SOUBORY, VSTUPY A VÝSTUPY POKRAČOVÁNÍ

Dědění, polymorfismus

Definice třídy. úplná definice. public veřejná třída abstract nesmí být vytvářeny instance final nelze vytvářet potomky

1 Webový server, instalace PHP a MySQL 13

Třídy, polymorfismus. A0B36PR2-Programování 2 Fakulta elektrotechnická České vysoké učení technické

IoC/DI. Tomáš Herceg Microsoft MVP (ASP.NET)

Transkript:

KAPITOLA 2 Návrhový vzor Singleton Jazyk PHP 5 vám pomocí klíčových slov public, protected a private umožňuje kontrolovat, kdo získá přístup k určitým atributům a metodám třídy. Dále vám jazyk PHP 5 umožňuje omezit to, co může být při odvozování tříd přepsané, a dokonce i to, co přepsané být musí. Jedno vám však jazyk PHP neumožňuje: nedokážete totiž omezit počet instancí dané třídy. A právě k tomuto účelu slouží návrhový vzor Singleton. Návrhový vzor Singleton je jeden z nejjednodušších návrhových vzorů a zároveň i jeden z nejpoužívanějších. Možná jste jej už použili, aniž byste věděli, že se jedná o návrhový vzor. Problém Ve vzorovém příkladu z předchozí kapitoly jste vyčlenili kód pro ladění aplikací ze třídy Library a zapouzdřili jej do nové třídy. Tím jste umožnili tomuto kódu spolupracovat i s ostatními třídami. Při vytváření instance třídy Library jednoduše předáte implementaci rozhraní Debugger konstruktoru: $debugger = new DebuggerEcho(); $library = new Library($debugger); 49

Část II Tvořivé vzory Chcete-li odladit i třídy Book a Member, může příslušný kód vypadat následovně: $debuggerbook = new DebuggerEcho(); $book = new Book($debuggerBook, 'PC', 100); $debuggermember = new DebuggerEcho(); $member = new Member($debuggerMember, 1, 'Marian Böhmer'); Tímto způsobem jste vytvořili tři instance stejné třídy a tím spotřebovali skoro třikrát tolik paměti. Knihovna však obsahuje určitě více než jednu publikaci a má registrovaných více než jednoho člena. Spotřeba paměti tedy bude se zvyšujícím se počtem publikací a členů knihovny neustále narůstat. Když se ale na objekt Debugger podíváte blíže, hned vás napadne, že není potřebné pro každou publikaci a každého člena používat samostatný objekt typu Debugger; tento objekt se nakonec používá jen na vypsání předaných hlášení a neví nic o objektu, který jej používá. Ideální by bylo, kdybyste mohli pro každou publikaci a každého člena knihovny používat stejný objekt typu Debugger tím byste ušetřili množství paměti a vyhnuli se zbytečnému vytváření dalších objektů. S růstem knihovny budou vytvářené objekty typu Debugger na stále nových místech, a nikdy tak nebudete mít jistotu, který z nich byl vytvořený jak první. V takovém případě potřebujete k objektu typu Debugger centrální přístupový bod. Účel návrhového vzoru Tohoto centrálního přístupového bodu docílíte pomocí návrhového vzoru Singleton. Návrhový vzor Singleton zajistí, že z určité třídy může existovat nejvíce jedna instance, a poskytne k ní globální přístupový bod. Pro aplikování tohoto vzoru na výše popsaný problém je nutné vykonat následující kroky: 1. Poskytnout centrální bod pro přístup k instanci třídy Debugger. 2. Tento centrální přístupový bod musí vždy nabízet přístup ke stejnému objektu nezávisle na počtu volaní. 3. Zabránit možnosti vytvoření další instance třídy. 50

Kapitola 2 Návrhový vzor Singleton Implementace Vytvoříte-li v kódu objekt typu Debugger na místě, kde jej chcete použít, nemáte žádnou možnost zji stit, zda už daný objekt existuje. Pro vytvoření instance objektu na centrálním místě přesuňte tento kód do nové metody. Protože k vytvoření objektu nejsou nutné žádné další informace, použijte k tomu statickou metodu, tj. metodu třídy, kterou lze zavolat bez nutnosti vytvoření instance této třídy: class DebuggerEcho implements Debugger { public static function getinstance() { $debugger = new self(); return $debugger; print $message. "\n"; Ve statické metodě getinstance() vytvoříte novou instanci třídy DebuggerEcho, kterou následně vrátíte. Místo vytvoření objektu typu Debugger pomocí operátoru new můžete nyní k tomuto účelu použít novou metodu: use cz\k1886\debuggers\debuggerecho; $debugger1 = DebuggerEcho::getInstance(); $debugger1->debug('lorem ipsum dolor sit amet.'); $debugger2 = DebuggerEcho::getInstance(); $debugger2->debug('proin fringilla bibendum sagittis.'); if ($debugger1 === $debugger2) { print '$debugger1 === $debugger2'; else { print '$debugger1!== $debugger2'; V tomto příkladu objekt nevytváříme přímo v místě, kde jej potřebujeme, nicméně počet vytvořených objektů se nezměnil, protože při každém volání metody getinstance() se vytvoří nový objekt typu Debugger. To potvrzuje i výpis z příkladu: 51

Část II Tvořivé vzory Lorem ipsum dolor sit amet. Proin fringilla bibendum sagittis. $debugger1!== $debugger2 Z toho vyplývá, že metoda getinstance() by měla obsahovat více logiky a při každém volaní provést následující kroky: 1. Při volání ověřit, zda už existuje instance třídy. 2. Pokud ne, vytvořit instanci třídy pomocí operátoru new a uložit ji. 3. Vrátit uloženou instanci. Aby bylo možné použít stejný objekt vícekrát, musí být uložený v rámci metody getinstance(). Vzhledem k tomu, že tato metoda je definovaná jako statická, musí být atribut, který bude uchovávat instanci dané třídy, také definovaný jako statický: class DebuggerEcho implements Debugger { private static $instance = null; public static function getinstance() { if (null == self::$instance) { self::$instance = new self(); return self::$instance; print $message. "\n"; Třídu DebuggerEcho jste v tomto kroku doplnili o statický atribut $instance. Metoda getinstance() při jejím volání ověří, zda $instance již obsahuje instanci třídy. Pokud ne, vytvoří se nová instance, která se přiřadí do atributu a kterou následně metoda vrátí. Při opětovném provedení testovacího skriptu již docílíme požadovaného efektu: Lorem ipsum dolor sit amet. Proin fringilla bibendum sagittis. $debugger1 === $debugger2 52

Kapitola 2 Návrhový vzor Singleton V této chvíli je úplně jedno, jak často se bude metoda getinstance() volat, aplikace bude používat vždy stejný objekt, čímž se ušetří systémové prostředky. Skryté problémy Návrhový vzor Singleton s sebou nese i pár skrytých problémů. Co se stane, pokud vaši týmoví kolegové nebudou vědět, že pro získání objektu typu Debugger mají použít metodu getinstance()? Vytvoří jej klasickým způsobem: use cz\k1886\debuggers\debuggerecho; // váš objekt debugger1 $debugger1 = DebuggerEcho::getInstance(); $debugger1->debug('lorem ipsum dolor sit amet.'); // objekt debugger2 vašeho kolegy $debugger2 = new DebuggerEcho(); $debugger2->debug('proin fringilla bibendum sagittis.'); if ($debugger1 === $debugger2) { print '$debugger1 === $debugger2'; else { print '$debugger1!== $debugger2'; Vytvořené objekty znovu nejsou stejné a znovu dochází k plýtvání pamětí. Musíte tedy svým týmovým kolegům zakázat možnost vytvářet instance samostatně a tím je donutit používat metodu getinstance(). Řešení tohoto problému je velmi jednoduché. Použití konstruktoru mimo třídu zakážete tak, že jej deklarujete jako protected: class DebuggerEcho implements Debugger { //... statický atribut a metoda getinstance() protected function construct() { print $message. "\n"; 53

Část II Tvořivé vzory Pokud se v této chvíli pokusí některý z vašich kolegů vytvořit novou instanci objektu typu Debugger, zareaguje na to interpret jazyka PHP následující chybou: Fatal error: Call to protected cz\k1886\debuggers\debuggerecho:: construct() from invalid context in test.php on line 10 Váš kolega bude muset místo toho použít k získání objektu metodu getinstance(). Vynalézaví kolegové, kteří přesto budou chtít vytvořit novou instanci objektu, by mohli přijít na myšlenku klonovat objekt, který získají pomocí metody getinstance(): use cz\k1886\debuggers\debuggerecho; $debugger1 = DebuggerEcho::getInstance(); $debugger1->debug('lorem ipsum dolor sit amet.'); $debugger2 = clone $debugger1; $debugger2->debug('proin fringilla bibendum sagittis.'); if ($debugger1 === $debugger2) { print '$debugger1 === $debugger2'; else { print '$debugger1!== $debugger2'; Po provedení tohoto testovacího skriptu se znovu ukáže, že se používají dvě instance třídy. I když vám nikdo takovéto kolegy nepřeje, nemůžete si být se současným řešením problému nikdy jistí, že skutečně existuje jen jedna instance třídy. Z toho vyplývá, že jste požadavek číslo tři zatím nesplnili na 100 %. Naštěstí vám jazyk PHP také umožňuje změnit chování třídy při klonování, k čemuž stačí implementovat metodu s názvem clone(). Klonování objektu zakážete tím, že zakážete volání metody clone() mimo danou třídu: class DebuggerEcho implements Debugger { //... statický atribut a metoda getinstance() protected function construct() { private function clone() { 54

Kapitola 2 Návrhový vzor Singleton print $message. "\n"; Při opakovaném pokusu klonovat objekt typu Debugger obdrží váš kolega chybové hlášení, které mu oznamuje, že klonování objektu je zakázané: Fatal error: Call to private cz\k1886\debuggers\debuggerecho:: clone() from context '' in test.php on line 10 Tím jste zabezpečili, že vždy může existovat maximálně jeden objekt třídy DebuggerEcho, a implementovali jste tudíž svého prvého jedináčka. POZNÁMKA Možná se divíte, proč byl konstruktor deklarovaný jako protected a metoda clone() jako private. Důvodem jsou možnosti, které poskytuje dědění. V případě konstruktoru chcete umožnit třídám, které jsou potomky třídy DebuggerEcho, aby jej mohly přepsat. Takto deklarovaný konstruktor není sice možné použít mimo třídu, avšak je možné jej použít na jeho obvyklé úlohy. V případě metody clone() toto není nutné a má být jen zamezení její používání. Definice Návrhový vzor Singleton zajistí, že z určité třídy může existovat nejvíce jedna instance, a poskytne k ní globální přístupový bod. Pro implementaci tohoto návrhového vzoru jsou vždy nutné následující čtyři kroky: 1. Definovat statický atribut, který obsahuje instanci objektu třídy. 2. Implementovat statickou metodu, která vrací objekt z bodu 1 a v případě, že neexistuje, jej vytvoří. 3. Zabránit vytvoření nové instance pomocí operátoru new tak, že bude konstruktor deklarovaný jako protected. 4. Zajistit, aby objekt třídy nemohl být klonovaný, k čemuž stačí metodu clone() deklarovat jako private. Na obrázku 2.1 je zobrazený diagram jazyka UML pro obecný návrhový vzor Singleton a na obrázku 2.2 je jeho konkrétní implementace. 55

Část II Tvořivé vzory Singleton -instance : Singleton -attribute1 -attribute2 #Singleton() +getinstance() : Singleton +method1() +method2() Obrázek 2.1: UML diagram návrhového vzoru Singleton DebuggerEcho -instance : DebuggerEcho #DebuggerEcho() +getinstance() : DebuggerEcho - clone() Obrázek 2.2: UML diagram konkrétní implementace Shrnutí Použití návrhového vzoru Singleton má pro vaši aplikaci následující dopady: Můžete přesně kontrolovat způsob, jakým se přistupuje ke třídě implementující tento návrhový vzor. Z dané třídy existuje vždy maximálně jedna instance. Druhou instanci třídy při použití tohoto návrhového vzoru není možné vytvořit. Existují však různé modifikace tohoto návrhového vzoru, u nichž je povolena i více než jedna instance. Návrhový vzor Singleton vám umožňuje zredukovat počet globálních proměnných nebo je ze zdrojového kódu úplně odstranit. Místo ukládání globálních instancí tříd v globálním oboru názvů můžete přistupovat k jejich instancím přes statické metody, čímž udržujete globální obor názvů volný. Návrhový vzor Singleton je jeden z nejjednodušších návrhových vzorů, což může být i důvodem, proč je využívaný i v situacích, kdy to není nutné. Před jeho nasazením byste se proto měli zamyslet, zda skutečně povolíte jen jednu instanci třídy, nebo zda je užitečné současné využití více instancí a ve skutečnosti potřebujete jen centrální přístupový bod k objektu. V posledním případě byste k tomu mohli využít i návrhový vzor Registry (Registr). 56

Kapitola 2 Návrhový vzor Singleton Další využití Kromě objektu typu Debugger existují i další možnosti, kde lze využít návrhový vzor Singleton. Často se například využívá při poskytování centrálního přístupového bodu ke konfiguraci aplikace. Při existenci jen jednoho objektu, který se stará o ukládání konfigurace, získáte hned dvě výhody: Pokud se změní nějaká konfigurační hodnota komponenty, platí tato změna okamžitě pro všechny komponenty. Konfigurační soubory stačí načíst jen jednou, což se kladně projeví na spotřebě dostupných zdrojů. Stejně často se návrhový vzor Singleton používá, pokud aplikace přistupuje k externím zdrojům, jako jsou například databáze. Použitím jednoho centrálního objektu, který se stará o přístup k databázi, stačí otevřít jen jedno připojení, které si pak jednotlivé komponenty sdílejí. Variace návrhového vzoru Singleton Možná jste si říkali, jak se dá použít návrhový Singleton pro objekty, které mají být z vnějšku parametrizované. Chcete-li například znovu změnit způsob ladění aplikace na protokolování do souboru, avšak nezapisovat všechna hlášení do jednoho souboru, lze použít pro různé komponenty různé soubory. Objekt typu Debugger pro protokolování do souboru by mohl vypadat následovně: class DebuggerLog implements Debugger { protected $logfile = null; public function construct($logfile) { $this->logfile = $logfile; error_log($message. "\n", 3, $this->logfile); 57

Část II Tvořivé vzory Při vytváření instance třídy DebuggerLog se konstruktoru předá název souboru, do něhož se mají předaná hlášení zapisovat. Díky tomu lze zapisovat hlášení do různých souborů. use cz\k1886\debuggers\debuggerlog; $debugger1 = new DebuggerLog('./debugger1.log'); $debugger1->debug('lorem ipsum dolor sit amet.'); $debugger2 = new DebuggerLog('./debugger2.log'); $debugger2->debug('proin fringilla bibendum sagittis.'); Jenže v tomto případě již nemůžete využít vzor Singleton, protože chcete povolit více než jednu instanci. To ale chcete umožnit jen v případě, kdy různé instance zapisují do různých souborů. V případě zápisu do stejného protokolovacího souboru by se měla použít stejná instance. Jednoduchou změnou implementace návrhového vzoru Singleton je i toto možné. Místo jedné globální instance musíte do statického atributu uložit instanci třídy podle použitého souboru. Tento atribut tedy vyměníte za pole: class DebuggerLog implements Debugger { protected $logfile = null; private static $instances = array(); public static function getinstance($logfile) { if (!isset(self::$instances[$logfile])) { self::$instances[$logfile] = new self($logfile); return self::$instances[$logfile]; protected function construct($logfile) { $this->logfile = $logfile; private function clone() { error_log($message. "\n", 3, $this->logfile); 58

Kapitola 2 Návrhový vzor Singleton Nyní už stačí jen předat název protokolovacího souboru metodě getinstance(). Následující příklad vytvoří dvě instance třídy DebuggerLog, přestože se metoda getinstance() bude volat třikrát: use cz\k1886\debuggers\debuggerlog; $debugger1 = DebuggerLog::getInstance('./debugger1.log'); $debugger1->debug('lorem ipsum dolor sit amet.'); $debugger2 = DebuggerLog::getInstance('./debugger2.log'); $debugger2->debug('proin fringilla bibendum sagittis.'); $debugger3 = DebuggerLog::getInstance('./debugger1.log'); $debugger3->debug('mauris vitae augue dolor.'); První a poslední volání vrátily stejný objekt, o čemž se můžete přesvědčit i na obrázku 2.3. Obrázek 2.3: Pole instancí upravené implementace návrhového vzoru Singleton Sice jste zde neimplementovali vzor Singleton v klasické podobě, nicméně s touto variací se budete v praxi střetávat velmi často. 59