W jaki sposób organizujesz i zarządzasz obiektami pomocniczymi, takimi jak silnik bazy danych, powiadamianie użytkownika, obsługa błędów itp. W projekcie obiektowym opartym na PHP?
Powiedzmy, że mam duży PHP CMS. CMS jest zorganizowany w różnych klasach. Kilka przykładów:
- obiekt bazy danych
- Zarządzanie użytkownikami
- API do tworzenia / modyfikowania / usuwania elementów
- obiekt wiadomości służący do wyświetlania komunikatów dla użytkownika końcowego
- moduł obsługi kontekstu, który przenosi Cię na właściwą stronę
- klasa paska nawigacji, która wyświetla przyciski
- obiekt rejestrowania
- ewentualnie niestandardowa obsługa błędów
itp.
Mam do czynienia z odwiecznym pytaniem, jak najlepiej udostępnić te obiekty każdej części systemu, która tego potrzebuje.
Moje pierwsze rozwiązanie, wiele lat temu, polegało na stworzeniu globalnej aplikacji $ application zawierającej zainicjowane instancje tych klas.
global $application;
$application->messageHandler->addMessage("Item successfully inserted");
Następnie przeszedłem na wzór Singleton i funkcję fabryczną:
$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");
ale to też mi się nie podoba. Testy jednostkowe i hermetyzacja stają się dla mnie coraz ważniejsze, aw moim rozumieniu logika stojąca za globalnymi / singletonami niszczy podstawową ideę OOP.
Wtedy oczywiście istnieje możliwość nadania każdemu obiektowi szeregu wskaźników do potrzebnych mu obiektów pomocniczych, prawdopodobnie w najczystszy, oszczędzający zasoby i przyjazny dla testowania sposób, ale mam wątpliwości co do możliwości utrzymania tego w dłuższej perspektywie.
Większość frameworków PHP, które przyjrzałem się, używa wzorca singleton lub funkcji, które mają dostęp do zainicjowanych obiektów. Oba dobre podejścia, ale jak powiedziałem, nie jestem zadowolony z żadnego.
Chciałbym poszerzyć swoje horyzonty dotyczące tego, jakie wspólne wzorce istnieją tutaj. Szukam przykładów, dodatkowych pomysłów i wskazówek dotyczących zasobów, które omawiają to z długoterminowej , rzeczywistej perspektywy.
Interesują mnie również specjalistyczne, niszowe lub po prostu dziwne podejścia do problemu.
źródło
$mh=&factory("messageHandler");
jest bezcelowe i nie daje żadnych korzyści wydajnościowych. Ponadto jest to przestarzałe w 5.3.Odpowiedzi:
Unikałbym podejścia Singletona sugerowanego przez Flaviusa. Istnieje wiele powodów, dla których należy unikać tego podejścia. Narusza dobre zasady OOP. Blog testujący google zawiera dobre artykuły na temat Singletona i jak go uniknąć:
http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html
Alternatywy
usługodawcą
http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html
zastrzyk zależności
http://en.wikipedia.org/wiki/Dependency_injection
i wyjaśnienie php:
http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection
To jest dobry artykuł o tych alternatywach:
http://martinfowler.com/articles/injection.html
Implementowanie iniekcji zależności (DI):
Uważam, że powinieneś zapytać, co jest potrzebne w konstruktorze, aby obiekt działał :
new YourObject($dependencyA, $dependencyB);
Możesz podać potrzebne obiekty (zależności) ręcznie (
$application = new Application(new MessageHandler()
). Ale możesz także użyć frameworka DI (strona wikipedii zawiera linki do frameworków PHP DI ).Ważne jest, aby przekazać tylko to, czego faktycznie używasz (wywołanie akcji), a NIE to, co po prostu przekazujesz innym obiektom, ponieważ tego potrzebują. Oto niedawny post od „wujka Boba” (Roberta Martina) omawiający ręczne DI a używanie frameworka .
Jeszcze kilka przemyśleń na temat rozwiązania Flaviusa. Nie chcę, aby ten post był antypostem, ale myślę, że ważne jest, aby zobaczyć, dlaczego zastrzyk zależności jest, przynajmniej dla mnie, lepszy niż globals.
Mimo że nie jest to „prawdziwa” implementacja Singletona , nadal uważam, że Flavius źle to zrozumiał. Globalny stan jest zły . Zwróć uwagę, że takie rozwiązania wykorzystują również trudne do przetestowania metody statyczne .
Wiem, że wiele osób to robi, aprobuje i używa. Ale czytając artykuły na blogu Misko Heverys ( ekspert Google w zakresie testowania ), ponowne ich przeczytanie i powolne przetrawienie tego, co mówi, bardzo zmieniło sposób, w jaki postrzegam projektowanie.
Jeśli chcesz mieć możliwość przetestowania swojej aplikacji, musisz przyjąć inne podejście do projektowania aplikacji. Podczas programowania w pierwszej kolejności będziesz miał trudności z takimi rzeczami: „następnie chcę zaimplementować rejestrowanie w tym fragmencie kodu; napiszmy najpierw test, który rejestruje podstawową wiadomość, a następnie wymyślmy test, który zmusza cię do napisania i używania globalnego rejestratora, którego nie można zastąpić.
Wciąż zmagam się ze wszystkimi informacjami, które otrzymałem z tego bloga, i nie zawsze jest to łatwe do wdrożenia i mam wiele pytań. Ale nie ma sposobu, abym mógł wrócić do tego, co robiłem wcześniej (tak, stan globalny i singletony (duże S)) po tym, jak zrozumiałem, co mówił Misko Hevery :-)
źródło
Tak bym to zrobił. Tworzy obiekt na żądanie:
To sposób, w jaki to robię, przestrzega zasad OOP, jest mniej kodu niż sposób, w jaki robisz to teraz, a obiekt jest tworzony tylko wtedy, gdy kod potrzebuje tego po raz pierwszy.
Uwaga : to, co przedstawiłem, nie jest nawet prawdziwym singletonem. Singleton dopuszczałby tylko jedno wystąpienie samego siebie, definiując konstruktor (Foo :: __ constructor ()) jako prywatny. Jest to tylko zmienna „globalna” dostępna dla wszystkich instancji „Aplikacji”. Dlatego uważam, że jego użycie jest ważne, ponieważ NIE lekceważy dobrych zasad OOP. Oczywiście, jak wszystko na świecie, ten „wzorzec” również nie powinien być nadużywany!
Widziałem, jak to jest używane w wielu frameworkach PHP, między innymi Zend Framework i Yii. I powinieneś użyć frameworka. Nie powiem ci, który.
Dodatek Dla tych z was, którzy martwią się o TDD , nadal możecie stworzyć okablowanie, aby wstrzyknąć to zależność. Mogłoby to wyglądać tak:
Jest wystarczająco dużo miejsca na ulepszenia. To tylko PoC, użyj swojej wyobraźni.
Dlaczego tak się dzieje? Cóż, przez większość czasu aplikacja nie będzie testowana jednostkowo, w rzeczywistości będzie uruchamiana, miejmy nadzieję, w środowisku produkcyjnym . Siłą PHP jest szybkość. PHP NIE jest i nigdy nie będzie „czystym językiem OOP”, takim jak Java.
W aplikacji jest tylko jedna klasa Application i tylko jedno wystąpienie każdego z jej pomocników (zgodnie z leniwym ładowaniem, jak powyżej). Jasne, singletony są złe, ale z drugiej strony tylko wtedy, gdy nie trzymają się prawdziwego świata. W moim przykładzie tak.
Źródłem zła są stereotypowe „zasady”, takie jak „singletony są złe”, są dla leniwych ludzi, którzy nie chcą myśleć samodzielnie.
Tak, wiem, manifest PHP jest ZŁY, technicznie rzecz biorąc. Jednak jest to język odnoszący sukcesy na swój hackerski sposób.
Uzupełnienie
Jeden styl funkcji:
źródło
Podoba mi się koncepcja Dependency Injection:
Fabien Potencier napisał naprawdę fajną serię artykułów o Dependency Injection i potrzebie ich stosowania. Oferuje również ładny i mały pojemnik na iniekcje Dependency Injection o nazwie Pimple, z którego bardzo lubię korzystać (więcej informacji na github ).
Jak wspomniano powyżej, nie podoba mi się używanie Singletonów. Dobre podsumowanie tego, dlaczego Singletony nie są dobrym projektem, można znaleźć tutaj na blogu Steve'a Yegge'a .
źródło
decupling from GOD object
: stackoverflow.com/questions/1580210/… z bardzo ładnym przykłademNajlepszym podejściem jest posiadanie pewnego rodzaju pojemnika na te zasoby. Niektóre z najczęstszych sposobów implementacji tego kontenera :
Singel
Niezalecane, ponieważ jest trudne do przetestowania i sugeruje stan globalny. (Zapalenie singletonitis)
Rejestr
Eliminuje singletonitis, błąd, którego też nie polecam, ponieważ jest to też rodzaj singletona. (Trudny test jednostkowy)
Dziedzictwo
Szkoda, w PHP nie ma dziedziczenia wielokrotnego, więc ogranicza to wszystko do łańcucha.
Wstrzyknięcie zależności
To lepsze podejście, ale szerszy temat.
Tradycyjny
Najprostszym sposobem na to jest użycie iniekcji konstruktora lub ustawiającego (obiekt zależności pass za pomocą settera lub w konstruktorze klasy).
Ramy
Możesz rzucić swój własny wstrzykiwacz zależności lub użyć niektórych struktur iniekcji zależności, np. Yadif
Zasób aplikacji
Możesz zainicjować każdy z zasobów w aplikacji ładującej (która działa jak kontener) i uzyskać do nich dostęp w dowolnym miejscu aplikacji uzyskując dostęp do obiektu ładowania początkowego.
To jest podejście zaimplementowane w Zend Framework 1.x
Moduł ładujący zasoby
Rodzaj statycznego obiektu, który ładuje (tworzy) potrzebny zasób tylko wtedy, gdy jest potrzebny. To bardzo sprytne podejście. Możesz zobaczyć to w akcji, np. Implementując komponent Dependency Injection w Symfony
Iniekcja do określonej warstwy
Zasoby nie zawsze są potrzebne w aplikacji. Czasami po prostu są potrzebne np. W sterownikach (MV C ). Wtedy możesz wstrzyknąć zasoby tylko tam.
Typowym podejściem do tego jest używanie komentarzy docblock w celu dodania metadanych wstrzykiwania.
Zobacz moje podejście do tego tutaj:
Jak używać wstrzykiwania zależności w Zend Framework? - Przepełnienie stosu
Na koniec chciałbym dodać uwagę o bardzo ważnej rzeczy - buforowaniu.
Ogólnie rzecz biorąc, pomimo wybranej techniki, należy pomyśleć o sposobie buforowania zasobów. Pamięć podręczna będzie sama w sobie zasobem.
Aplikacje mogą być bardzo duże, a ładowanie wszystkich zasobów przy każdym żądaniu jest bardzo kosztowne. Istnieje wiele podejść, w tym ten appserver-in-php - Project Hosting on Google Code .
źródło
Jeśli chcesz, aby obiekty były dostępne globalnie, wzorzec rejestru może być dla Ciebie interesujący. Aby uzyskać inspirację, zajrzyj do Zend Registry .
Więc także kwestia Rejestr kontra Singleton .
źródło
Obiekty w PHP zajmują dużo pamięci, jak zapewne zauważyłeś na podstawie testów jednostkowych. Dlatego idealnym rozwiązaniem jest jak najszybsze zniszczenie niepotrzebnych obiektów, aby zaoszczędzić pamięć dla innych procesów. Mając to na uwadze, stwierdzam, że każdy przedmiot pasuje do jednej z dwóch form.
1) Obiekt może mieć wiele przydatnych metod lub musi być wywoływany więcej niż raz, w którym to przypadku implementuję singleton / rejestr:
2) Obiekt istnieje tylko przez cały okres istnienia metody / funkcji wywołującej go, w którym to przypadku proste utworzenie jest korzystne, aby zapobiec utrzymywaniu obiektów przy życiu przez długotrwałe odniesienia do obiektów.
Przechowywanie tymczasowych obiektów GDZIEKOLWIEK może prowadzić do wycieków pamięci, ponieważ można zapomnieć o odwołaniach do nich o utrzymywaniu obiektu w pamięci przez resztę skryptu.
źródło
Wybrałbym funkcję zwracającą zainicjalizowane obiekty:
W środowisku testowym można zdefiniować zwracanie makiet. Możesz nawet wykryć wewnątrz, kto wywołuje funkcję za pomocą debug_backtrace () i zwraca różne obiekty. Możesz zarejestrować w nim, kto chce uzyskać jakie obiekty, aby uzyskać wgląd w to, co faktycznie dzieje się w twoim programie.
źródło
Dlaczego nie przeczytać dobrej instrukcji?
http://php.net/manual/en/language.oop5.autoload.php
źródło