![]() |
![]() |
![]()
Post
#1
|
|
Grupa: Zarejestrowani Postów: 743 Pomógł: 0 Dołączył: 11.11.2003 Skąd: Toruń Ostrzeżenie: (0%) ![]() ![]() |
Witam,
znów mam problem z zaplanowaniem przepływu wszystkich danych w aplikacji. Wedle poprzednich podpowiedzi, które uzyskałem na forum zrobiłem tak (na przykładzie dodawania newsa): NewsModel.php
NewsController.php
No i niby wszystko dziala ladnie, mam kontroler ktory przekazuje dane do modelu, model te dane zapisuje i wszystko jest good. Ale teraz gdy planuję dodać nową kolumnę w bazie danych, muszę modyfikować Model, wszystkie kontrolery które uzyły danego Modelu. Dodatkowo jeśli gdzieś nie zmienię kontrolera to np. content zapisze mi sie jako title, bo zmieni sie kolejnosc zmiennych wchodzących do funkcji i wszystko sie pokrzaczy. Dobrze to robię, twrząc dla każdego zadania nową funkcję w modelu i przekazując do niej dane jako parametry funkcji? I też pytanie czy to nie jest bez sensu takie duplikowanie wielu funkcji które dostarcza mi ORM Propel. Np. żeby pobrać newsa o danym id, napisąłem funkcję w modelu:
Ale przecież równie dobrze mogłem od razu w kontrolerze pobrać newsa za pomocą funkcji dostarczonych przez ORM:
Jakie mam korzyści z przerzucania zadań do modelu. Nie dodaję sobie tym zbędnej pracy, pisząc masę metod które w sumie już posiadam? Ten post edytował Kuziu 14.02.2011, 12:20:02 |
|
|
![]() |
![]()
Post
#2
|
|
Grupa: Zarejestrowani Postów: 6 476 Pomógł: 1306 Dołączył: 6.08.2006 Skąd: Kraków Ostrzeżenie: (0%) ![]() ![]() |
Cytat No i właśnie znów widzę że "a potem ktoś na forum.php.pl pisał mi żebym w żaden sposób nie kojarzył klas propela z modelem bo to jest zupełnie co innego :|" i piszesz że mądrze. A zaraz potem "Ale obiekt Propela to jest model". Jej, rzeczywiście namieszałem. Już prostuję... jedną z podstawowych cech MVC czy raczej nawet samego OOP jest komunikacja przy użyciu interfejsów. Dzięki takiemu podejściu możemy niemalże w każdym momencie zmienić implementację dowolnej klasy nie wpływając na działanie reszty aplikacji. Dobrze zaprojektowany interfejs nie ujawnia szczegółów implementacji klasy (np. faktu, że korzysta ona z bazy danych do zapisywania danych).To ja już nic nie rozumiem. Czym jest model? Model to warstwa aplikacji odpowiedzialna głównie za obsługę obiektów biznesowych (dziwnie brzmi polskie tłumaczenie :]) czyli obiektów reprezentujących dane w aplikacji. Oczywiście w skład modelu wchodzi jeszcze cały wachlarz obiektów potrzebnych do zarządzania tym wszystkim. Tak więc bardzo uproszczona warstwa modelu może składać się z dwóch typów klas - encji reprezentujących obiekty biznesowe oraz usług/menadżerów pozwalających wykonywać na nich pewne operacje (np. ich zapisanie, skasowanie czy pobranie - to ostatnie może być realizowane przez inny obiekt). Do tego dochodzą jeszcze interfejsy, które są implementowane przez poszczególne klasy modelu: Jak widzisz taki model ma na tyle dobrze zaprojektowany interfejs, że nie ujawnia on żadnych informacji n/t implementacji. Z punktu widzenia kontrolera nie ma najmniejszego znaczenia czy model wykorzystuje ORM-a typu Propel do zapisu danych w bazie czy może dane te zapisywane są w pliku XML albo i przesyłane są przez Internet do Facebooka. Dodatkowo jak widzisz unikałem bezpośredniego nawiązania do klas - zamiast tego wszystko opiera się na interfejsach. Metoda ArticleInterface::setUser() czy UserServiceInterface::persistUser() oczekują, że jako pierwszy parametr zostanie podany obiekt implementujący interfejs UserInterface, a nie konkretna klasa (User). Utworzenie nowego użytkownika (np. dla celów jego zarejestrowania) również nie wymaga jawnego podania nazwy klasy (new User()), a jedynie odwołania do UserServiceInterface::createUser() które zwróci jakiś obiekt implementujący UserInterface. Po co to wszystko? Po to, że w każdej chwili możesz do reprezentacji użytkownika zamiast klasy User wykorzystać MySuperNewUser - klasa ta musi jedynie implementować interfejs UserInterface i już jest zdatna do bezproblemowej pracy w aplikacji. W przypadku małych/prostych aplikacji takie podejście może wydać Ci się niepotrzebnie przekombinowane. Rzeczywiście, jeżeli piszesz bloga może to być zbędne, ale już w średniorozbudowanych projektach albo w projektach które będą rozwijane przez kilka lat powinno zacząć to procentować. Poza tym jeżeli wejdzie Ci to w nawyk nawet nie zauważysz dodatkowego narzutu pracy związanego z utworzeniem i projektowaniem interfejsów. A teraz wracając do Propela. Powinieneś zauważyć, że klasa Article wygenerowana przez Propela to właśnie klasa która powinna implementować ArticleInterface, a ArticleQuery to klasa która powinna implementować ArticleRepositoryInterface (obiekt zwracany przez metode getRepository()). Propel zbudowany jest w oparciu o wzorzec ActiveRecord, który przewiduje że to encja (obiekt Article) odpowiedzialna jest za reprezentowanie danych (setTitle(), getContent()) i ich zarządzanie (save(), delete()) - to już jest pierwszy błąd bo nagle obiekt odpowiedzialny jest za więcej niż jedno zadanie. W moim przykładzie to ostatnie zadanie realizowane jest przez osobny obiekt - UserService. Ale pomińmy tą moim zdaniem dosyć poważną niedogodność i przejdźmy do przykładowego kodu: Jak widzisz są tu dwa problemy. Po pierwsze model przestał opierać się na interfejsach, a zaczął na klasach Propela. Dodatkowo ujawniona została implementacja, a interfejs został wymuszony. Widać, że Propel nie może być być utożsamiany z modelem - nie nadaje się on do tego. Co zatem począć? Propela można co najwyżej wykorzystać jako narzędzie używane w naszym modelu. Innymi słowy - trzeba sprawić by Propel był tylko częścią implementacji, a nie interfejsu. By to osiągnąć konieczne by było utworzenie własny obiektów, które w swoim wnętrzu (w implementacji) korzystałyby z Propela. Dlatego też napisałem post wcześniej (pierwsza gwiazdka), że nie uważam Propela za najlepszy ORM do użycia w aplikacji MVC. Co prawda używałem go w wersji 1.2 lata temu, a nowa 1.5 znacząco się zmieniła z tego co widzę w dokumentacji nadal pozostały stare problemy. To jest dobry projekt, ale ciężko będzie go zmusić do pracy w tak mocno nastawionej na interfejsy architekturze. Cytat A co do singletonu z klasą user, to czy nie lepiej go mieć, zamiast przekazywać z kontrolera, do masy innych modeli czy innych kontrolerów? Nie lepiej jak każdy kontroler czy model który go potrzebuje po prostu pobierze go za pomocą getInstance? Pojawiają się dwa problemy:1. Tracisz właściwie wszystko to co zyskujesz dzięki interfejsom - patrz dosyć długi wywód powyżej. (IMG:style_emoticons/default/wink.gif) 2. Tworzysz "z dupy" zależności dla obiektów, ponieważ implementacja obiektów zaczyna korzystać z danych globalnych. Innymi słowy musisz liczyć się z tym, że chcąc przykładowo zmienić by użytkownik korzystał z klasy MyUserUser zamiast User będziesz musiał szukać miliona User::getInstance() tylko po to by zmienić to na MyUserUser::getInstance(), znacznie ciężej będzie Ci ponownie wykorzystywać dany kod, a tak podstawowe zadania jak przykładowo przeprowadzanie testów stanie się znacznie trudniejsze. Swoją drogą zadaniem singletona nie jest udostępnienie globalnego dostępu do danego obiektu, a zagwarantowanie istnienia tylko jednej instancji danej klasy, czyli nie takie coś: Tylko normalne wykorzystywanie obiektówki:
W ogóle używanie właściwości/metod statycznych (jaką jest getInstance() w implementacji singletona) powinno być bardzo dobrze uargumentowane i "obronione" przed użyciem. Statyczność jest wypaczeniem obiektowości, ale jest "złem" koniecznym. |
|
|
![]() ![]() |
![]() |
Aktualny czas: 11.10.2025 - 02:38 |