Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Singleton w praktyce
Forum PHP.pl > Forum > PHP > Object-oriented programming
lukasz91
Witam,
właśnie uczę się o wzorcu singleton. W teorii już wiem o co tam chodzi. Byłbym wdzięczny jakby ktoś lakonicznie przedstawił jak używać tego w praktyce smile.gif
wookieb
Nie używać tego w praktyce.
http://forum.php.pl/index.php?showtopic=122586&hl=
lukasz91
hmm w takim może polecicie jakieś materiały o budowaniu aplikacji? smile.gif Chodzi mi o odpowiednie wykorzystywanie wzorców, wydajną budowę itp
michaJlS
Całkiem fajne opisy:
http://www.oodesign.com/
starach
Cytat(wookieb @ 25.07.2011, 18:49:50 ) *
Ja tam korzystam z niego razem w połączeniu z kontekstem dzięki czemu nawet mając kilka kontekstów mają one tą samą klasę oczywiście zakładając że tak jak u mnie context jest czymś w rodzaju połączenia wzorca kontenera i fabryki.

W małych projektach w używaniu singletona nie ma nic złego tak na prawdę tylko puryści będą zawsze marudzić i dobrze w końcu do czegoś muszą się przydać. wink.gif

edit>
Może i nie widzę nic złego, ale sam tego nie robię tak na marginesie. tongue.gif
Rid
Context ,nie zawsze jest dobry szczególnie w połączeniu z obiektami serializowanymi.Miałem przebój przez to w jednej z kluczowych u mnie bibliotek.
wookieb
Bo nie serializuje się obiektów z kontekstem. Poza tym możliwość przekazanie obiektu przy deserializacji ISTNIEJE. Ale owe rozwiązanie jest bezsensowne ponieważ oznacza, że nie rozumiesz idei kontekstu.
Rid
To trzeba powiedzieć Panom z Microsoftu bo utworzyli bibliotekę w której "odwoływali się" do sesji poprzez Context i nie wzieli pod uwagę ,że ktoś może mieć sesje zapisywane do bazy danych,które jako obiekt z contextem są nieserailizowalne,a sesje z innymi niż podstawowe typy danych jak(int,string) trzeba serializować ,aby zapisać do bazy.Dlatego wole unikać contextów.
wookieb
Wartości typu resource (połączenia, curl, uchwyty plików) też nie możesz używać przy serializowaniu. Czy to znaczy, że nie będziesz ich używać bo są głupie? Nie. To po prostu taki zasób, który nie może być w ten sposób przechowywany koniec kropka.
A co do biblioteki to nie wiem jaką masz na myśli, ale jeżeli tak jest jak mówisz to jest po prostu dupna. Zresztą... nigdy nie znajdziesz w PHP idealnej fuzji wielu bibliotek i FW. Po prostu każdy ma swój cyrk, swoje małpy, swoje przyzwyczajenia.
Rid
Alternatywa dla context Thread Local Storage,co o tym sądzicie??-ale patrze na wiki i stwierdzam że to jeszcze egzotyka w php.
dariuszp
Dlaczego ktoś tu mówi że nie należy stosować wzorca Singleton ? Dziecinne podejście. Wzorzec ten jak każdy inny to NARZĘDZIE. Od programisty zależy jak użyje dane narzędzie. Więc jeżeli wie jak je użyć to może je zastosować jeżeli są ku temu powody.

Lakoniczne przedstawienie jak używać tego w praktyce ? Jeżeli z jakiegoś powodu chcesz wymusić na programiście by nie tworzył 2 instancji danego obiektu to Singleton Twoim przyjacielem jest. Jest on zwłaszcza pomocny przy otwartych projektach gdzie nie możesz programistę który coś spieprzył strzelić w potylicę i kazać mu poprawić. Więc jak nie masz potylicy programisty w zasięgu ręki to Singleton Twoim przyjacielem jest.
wookieb
Pisałeś kiedyś testy jednostkowe? Zajmowałeś się prawdziwym projektowaniem większych aplikacji/bibliotek których używa masa programistów (nie koderów, którzy podniecają się wszystkim co oszczędzi im tylko roboty "na tą chwilę")?
Sagnitor
Zadam pytanie ciut odbiegające od głównego tematu.

W wielu tematach w internecie przeczytałem o tym jakie to singletony i rejestry są złe. Z natury jestem 'perfekcjonistą' czyli zawsze chcę zaprojektować i napisać swoje aplikacje tak idealnie na ile to jest możliwe ;-). Podczas projektowania swojego frameworka, doszedłem niedawno do systemu logowania błędów, wyjątków, informacji. W tym momencie napotkałem niemały problem. Okazało się, że obiekt 'Loggera' potrzebuję w wielu miejscach aplikacji. Aby napisać to jak najlepiej stwierdziłem, że potrzebuję obiektu dlatego operowanie na klasie statycznej odpada. Tutaj do wyboru są singleton lub przekazywanie mojego obiektu parametrami (tworzenie zależności).

Możecie mnie nakierować jaką techniką się posłużyć w takim problemie?
by_ikar
Cytat(Sagnitor @ 31.08.2011, 13:06:12 ) *
Zadam pytanie ciut odbiegające od głównego tematu.

W wielu tematach w internecie przeczytałem o tym jakie to singletony i rejestry są złe. Z natury jestem 'perfekcjonistą' czyli zawsze chcę zaprojektować i napisać swoje aplikacje tak idealnie na ile to jest możliwe ;-). Podczas projektowania swojego frameworka, doszedłem niedawno do systemu logowania błędów, wyjątków, informacji. W tym momencie napotkałem niemały problem. Okazało się, że obiekt 'Loggera' potrzebuję w wielu miejscach aplikacji. Aby napisać to jak najlepiej stwierdziłem, że potrzebuję obiektu dlatego operowanie na klasie statycznej odpada. Tutaj do wyboru są singleton lub przekazywanie mojego obiektu parametrami (tworzenie zależności).

Możecie mnie nakierować jaką techniką się posłużyć w takim problemie?


Też jakiś czas temu miałem podobny problem. Jak obsłużyć błąd powiedzmy systemu szablonów, jeżeli obiekt systemu szablonów został utworzony po utworzeniu obiektu do obsługi błędów. Wyjść jest conajmniej kilka, o singleton i przekazywaniu parametrów już wspomniałeś, a możesz te obiekty jeszcze zrobić tak, żeby można było je utworzyć bez zależności. Następnie tworzyć te obiekty (logger, response, szablony itp) dopiero w obiekcie obsługującym wyjątki.
Rid
Ja się chyba bardziej zainteresuję wzorcem TLS cap.gif . Kurcze błędnie skrut napisałem,już poprawiłem.
Sagnitor
Cytat(Rid @ 31.08.2011, 15:06:29 ) *
Ja się chyba bardziej zainteresuję wzorcem TLC cap.gif .


Możesz podać pełną nazwę lub chociaż link do opisu tego wzorca?
Rid
Ja pracuję w C# wzorzec jest dopracowany,w rubym,Javie też,niestety z tego co patrzyłem jak jest w php to chyba mogę stwierdzic,że chyba jest w fazie "eksperymentalnej".Link jest w poprzednim moim poście.
Tutaj więcej o tym wzorcu jakby kogoś interesowało.
adbacz
Kurcze, tak czytam o tym singletonie ale nijak nie umiem go zastosować w życiu. przeczytałem kilka tematów tutaj na forum, kilka artykułów, temat na wiki oraz przejrzałem kod CodeIgnitera, ale nijak nie chce mi to grać u mnie. Może pokarzę pseudokod i ktos dojrzy błąd, którego ja nie widzę;/

Główny kontroler:
  1. class Main_Controller {
  2.  
  3. private static $oInstance;
  4. public $test = 'test';
  5.  
  6. public function __construct() {
  7. self::$oInstance =& $this;
  8. }
  9.  
  10. public static function &get_instance() {
  11. return self::$oInstance;
  12. }
  13. }


Osobna funkcja, której możliwe jest wywołanie dopiero po wywołaniu głównego kontrolera:
  1. function &get_instance() {
  2. return Main_Controller::get_instance();
  3. }


No i przykład, gdzie wyskakuje mi błąd. W głównej klasie, gdzie wszystko zostaje uruchomione, potrzebne klasy, ustawienia i właściwa klasa, wywołana od URLa. W konstruktorze, po załadowaniu wszystkich plików:
  1. //ciach
  2. /*linia 65*/$FW =& get_instance();
  3. //ciach
  4. /*linia 77*/echo $FW->test; //Test
  5. //ciach


Dostaję taki błąd:
Kod
Trying to get property of non-object in {link do pliku} on line 77
wookieb
W php 5 te wszystkie & jako wskaźniki referencji nie są już potrzebne
Na 100 nie tworzysz nowej instancji kontrolera przed wywołaniem funkcji get_instance. Poza tym wywołanie konstruktora powinno być zabronione z zewnątrz. Tak więc...
  1. class Main_Controller {
  2.  
  3. private static $oInstance;
  4. public $test = 'test';
  5.  
  6. private function __construct() {
  7. }
  8.  
  9. public static function get_instance() {
  10. if (!self::$oInstance) {
  11. self::$oInstance = new self();
  12. }
  13. return self::$oInstance;
  14. }
  15. }


Teraz obiektu MainController nie tworzysz poprzez
  1. new MainController();

tylko
  1. $controller = MainController::get_instance();

adbacz
Mógłbyś mi wytłumaczyć dlaczego powinno się uniemożliwiać dostęp do konstruktora? Pogłowiłem się trochę i faktycznie, przeoczyłem utworzenie instancji głównego kontrolera, przez to konstruktor sie nie uruchamiał (świetny błąd, mój... ;/). Przypatrzałem się i zastosowałem Twój kod, ale nie ma funkcji, ani klasy self(), na pewno jest to dobrze? Nie powinno być czasem self::$oInstance;? Nie znam się a wypadł mi błąd w Twoim kodzie więc się pytam z ciekawości.

Tak nawiasem to mój kod chodzi poprawnie (po paru poprawkach), tylko, że ja instancję obiektu (a raczej zmienną $this) przypisuję do statycznej zmiennej w konstruktorze i chciałbym wiedzieć bardzo dlaczego twierdzisz, albo skąd wiesz, że nie powinno się udostępniać konstruktora z zewnątrz. Teraz mi działa wszystko tak jak powinno, chociaż zdaję sobie sprawę, że może to być moje błędne myślenie - zaczynam dopiero bawić się w pisanie Frameworka i próbuję jakoś zrozumieć działanie CodeIgnitera i przy okazji czegoś się nauczyć.
Daiquiri
Cytat(adbacz @ 16.10.2011, 18:36:10 ) *
Mógłbyś mi wytłumaczyć dlaczego powinno się uniemożliwiać dostęp do konstruktora?

Bo w momencie gdy konstruktor jest prywatny nie masz możliwości stworzenia obiektu poprzez new tylko korzystając ze statycznej metody.
adbacz
Dobrze, a co gdy klasa dziedziczy po głównym kontrolerze? Nie moge sobie poradzić z tym, aby wszystko było ok gdy konstruktor jest prywatny, nic mi nie działa. Próbowałem stworzyć osobną funkcję (w klasie głównego kontrolera), która przypisze do pola $this->load instancję klasy, która ładuje mi inne pliki (klasy), ale w miejscu wywołania kontrolera głównego i właściwego nie działa (stwierdzam, że nie działa, bo mimo, że w tej funkcji znajduje się echo 'test';, nic nie wyświetla), a później w ogóle mi wyskakuje błąd, że nie można używać zmiennej $this w kontekście "nie obiektowym", mimo, że wywoływana funkcja znajduje się w klasie głównego kontrolera ;/
mike
Cytat(adbacz @ 16.10.2011, 22:39:03 ) *
Dobrze, a co gdy klasa dziedziczy po głównym kontrolerze?
Napisz to samo w dwóch dłuższych zdaniach. Dowiemy się co masz na myśli.
W chwili obecnej nie mam pojęcia o co pytasz bo na razie to brzmi jak "Opel Astra II to kolejna wersja deski rozdzielczej.". Pomieszanie z poplątaniem wynikające z tego, że klasy dziedziczą tylko po klasach.
adbacz
Mam główny kontroler, w którym na samym początku powinno być ustalane publiczne pole $this->load (może nie tyle co powinno, ale mam takie założenie), do którego przypisywana jest klasa ładowania innych. Na samym początku, gdy mam wyłowywany kod, gdzie wszystkie składowe FW są zbierane do kupy, dołączane pliki, ustawienia itd, w pewnym momencie ładuje plik Głownego kontrolera po czym kontroler wywołany patrząc na URL. Ogólnie rzecz biorąc, wszystkie mniejsze kontrolery dziedziczą po tym głównym, w którym właśnie jest funkcja get_instance() oraz gdzie w konstruktorze jest przypisywane to pole. I wszystko było dobrze. Tworzyłem nowy obiekt głównego kontrolera, tego który dziedziczy gówny kontroler a w nim miałem już wszystko co potrzebne do aplikacji (bo wszystko co potrzebne miałem już robione w konstruktorze). Ale gdy zrobiłem prywatny konstruktor w głównym kontrolerze, mimo, że odwołuję się do instancji Main_Controller::get_instance();, nic się nie dzieje, tak, jakby ta funkcja była pusta, nie mogę tworzyć pól tej klasy, a metody są niewidoczne. A to, że konstruktor jest prywatny, nie mogę odwołać się do klasy przez new. Później jak już tworzę obiekt właściwego kontrolera to wyskakują mi same błędy, że nie można użyć $this, albo że nie ma pola $this->load itd.

Może nie jestem świetny w tych sprawach, ale wystarczy mi odrobina tekstu dobrze napisanego a sam do tego dojdę, dlatego tak jest a nie inaczej.

PS. Jeśli nie powinno sie uzywać publicznego konstruktora w głównym kontrolerze, to tak mnie zastanawia fakt wykorzystania tego w CodeIgniterze. Skoro Wy twierdzicie, że nie powinno się tak robić to dlaczego projektanci poszli na to? Może dla tego, że tak jest prościej? Bo faktycznie, z publicznym kontruktorem wystarczy wywołać new Main_Controller() i już mamy wszystko załatwione, a z prywatnym mam bardzo problem i nawet nie wiem jak się za niego zabrać mad.gif
Noidea
Tzn. masz kod, który tworzy obiekt Main_Controllera, który na podstawie podanych parametrów decyduje który kontroler ma zostać utworzony i tworzy jego obiekt. Dodatkowo wszystkie klasy kontrolerów dziedziczą po Main_Controller.

Jeśli tak, to tworzysz na dobrą sprawę dwa obiekty Main_Controllera - zwykły i obiekt klasy pochodnej (jeśli nie łapiesz dlaczego, to musisz trochę więcej poczytać i poeksperymentować z dziedziczeniem i polimorfizmem) - a to jest sprzeczne ze wzorcem singleton. Dlatego gdy poprawnie zaimplementowałeś ten wzorzec, to wszystko zaczęło się sypać.

Zastanów się:
1. Czy na pewno potrzebny jest tutaj singleton. Chcesz wymusić istnienie co najwyżej jednego obiektu kontrolera, czy po prostu chcesz się móc do tego obiektu dostać z każdego miejsca w kodzie?
2. Czy na pewno twoje kontrolery są wyspecjalizowanymi odmianami Main_Controllera w taki sam sposób jak komunikat błędu jest wyspecjalizowaną odmianą komunikatu? Może po prostu jedynym podobieństwem jest słowo Controller w nazwie, a tak na prawdę zajmują się czymś zupełnie innym?
adbacz
Chyba źle wytłumaczyłem. najpierw jest uruchamiany plik index.php, gdzie są definiowane stałe i uruchamiany kod z Core.php. Tam sprawdzany jest URL i na jego podstawie uruchamiany jest odpowiedni kontroler, który dziedziczy po głównym. Ale główny kontroler jest tylko includowany, nie jest tworzona jego żadna instancja więc na dobrą sprawę on jest dostepny, ale nie jest tworzony przez new. Dobrze myślę? Teraz jak mamy załączony głowny kontroler, chcę pobrać jego instancję w pliku Core.php, ale tam nie widzi, żadnych metod ani pól głownego kontrolera. Może faktycznie nie jest tu potrzebny singleton, ale chciałem poprostu mieć możliwość załadowania innych klas za pomocą odpowiedniej klasy do tego napisanej, ale, żeby można było to zrobić w odpowiednich do tego miejscach w kodzie, przykładowo w każdym kontrolerze, czy to główny czy dziedziczący oraz w modelu. Chciałem, też mieć dostęp do tego w pliku Core.php, ale coś mi się zdaje, że tak się nie da, prawda?

Chciałem poprzez instancję głównego kontrolera ładować klasy, aby były widoczne w kontrolerze który dziedziczy po głównym. Powiedzmy, $this->load->model('model'); I pod $this->model->metoda() mamy dany model, i tak samo dla bibliotek, widoków itd.

Dziękuję, że macie cierpliwość do mnie. Chcę to poprostu zrozumieć i wprowadzić w życie, w swój kod.
Daiquiri
Nie mam pewności - ale czy Ty aby przypadkiem nie starasz się zrobić Dependency Injection? Dlaczego nie skorzystasz z np. interfejsu czy klasy abstrakcyjnej - pytam odnośnie tego głównego kontrolera.
Noidea
Już chyba wiem co chcesz zrobić z tym singletonem. I muszę cię zmartwić - nie zadziała ci to tak jak chcesz. Problem jest taki, że pola statyczne nie są w żaden sposób powiązane z obiektami. Tzn. można powiedzieć, że są współdzielone przez wszystkie obiekty klasy i obiekty jej klas pochodnych. Przeanalizuj ten kod (wymaga PHP 5.3, jeśli masz starszą wersję zamień new static na new self, ale to zrobi jeszcze większy burdel):
  1. <pre>
  2. <?php
  3.  
  4. class Main_Controller
  5. {
  6. private static $instance;
  7.  
  8. protected function __construct() { }
  9.  
  10. public static function getInstance()
  11. {
  12. if( !self::$instance )
  13. {
  14. self::$instance = new static();
  15. }
  16.  
  17. return self::$instance;
  18. }
  19. }
  20.  
  21. class Foo_Controller extends Main_Controller
  22. {
  23. public $foo = 123;
  24. }
  25.  
  26. class Bar_Controller extends Main_Controller
  27. {
  28. public $bar = 456;
  29. }
  30.  
  31. $fooInstance = Foo_Controller::getInstance();
  32. $barInstance = Bar_Controller::getInstance();
  33.  
  34. var_dump( $fooInstance );
  35. var_dump( $barInstance );
  36.  
  37. ?>
  38. </pre>


Rozwiązaniem jest zastosowanie wzorca fabryka.
Ewentualnie możesz przerobi singleton w taki sposób, żeby korzystał z tablicy nazwa_klasy => obiekt_klasy:
  1. <pre>
  2. <?php
  3.  
  4. class Main_Controller
  5. {
  6. private static $instances = array();
  7.  
  8. protected function __construct() { }
  9.  
  10. public static function getInstance()
  11. {
  12. $controllerClass = get_called_class();
  13. if( !array_key_exists( $controllerClass, self::$instances ) )
  14. {
  15. self::$instances[$controllerClass] = new static();
  16. }
  17.  
  18. return self::$instances[$controllerClass];
  19. }
  20. }
  21.  
  22. class Foo_Controller extends Main_Controller
  23. {
  24. public $foo = 123;
  25. }
  26.  
  27. class Bar_Controller extends Main_Controller
  28. {
  29. public $bar = 456;
  30. }
  31.  
  32. $fooInstance = Foo_Controller::getInstance();
  33. $barInstance = Bar_Controller::getInstance();
  34.  
  35. var_dump( $fooInstance );
  36. var_dump( $barInstance );
  37.  
  38. ?>
  39. </pre>


Do poczytania:
http://php.net/manual/en/language.oop5.lat...ic-bindings.php
get_called_class
adbacz
Wiecie co, naczytałem się o Wzorcach Projektowych, i tak doszłem do wniosku, że tutaj nie potrzenuję Singletona. Instancja tej klasy, to znaczy głównego kontrolera jest tworzona tylko raz jeden - gdy inicjuje kontroler (klasę) który dziedziczy po głównym i później metodę (rozpoznając po adresie URL) więc tak na dobra sprawę, mogę wszystko zrobić w konstruktorze Głównego kontrolera.

I teraz w każdej jednej klasie, czy to w modelach czy w widoku, będę miał dostęp do pola $this->load, a z niego już będe sobie ładował wszyskie inne klasy. Jak na razie to nie musze używać żadnego wzorca (chyba) bo nie potrzebuję, ale to nie oznacza, że nie będę kiedyś potrzebował.

PS. Gdyby nie niektóre posty to bym nawet nie doszedł do tego a zaimplementowałbym niepotrzebnie Singleton, i w dodatku zrobiłbym to źle, bo znając siebie brnąłbym w to tak, aby działało ale nie koniecznie by było tak jak powinno być.

Dziękuję za pomoc.
Orzeszekk
a powiedzcie mi czy takie cos to tez jest wzorzec projektowy i ma nazwe
czy to jest po prostu cachowanie biggrin.gif

  1. class JakisTamModelZ_MVC
  2. {
  3. [...]
  4.  
  5. private $temporary;
  6. public function bardzoZasobozernaFunkcjaObliczajacaCosWModelu()
  7. {
  8. if (!($this->temporary))
  9. {
  10. [... bardzo zmudne obliczenia, w koncu otrzymujemy $wynik_obliczen ...]
  11. $this->temporary = $wynik_obliczen;
  12. }
  13. return $this->temporary;
  14. }
  15.  
  16. [...]
  17. }
  18.  


odnosnie wątku to kiedys robiłem główną klasę MasterPage inicjowalem ją na początku i do kazdej innej klasy musialem bąbelkowo przekazywać:

$class = new Class1($masterPage, [inne parametry...]);
itd itp

to sie nazywa wstrzyknięcie zależności tak?


wydawalo mi sie ze takie podejscie jest nieprofesjonalne i pisac tyle razy te parametry i przepisalem aplikacje (wtedy jeszcze we wczesnej fazie) na singletony. Do bazy danych i do masterPage (w koncu tylko jedna strona html powstanie podczas tego zycia skryptu).
I czy to jest błąd?
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.