Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Eleganckie zastępowanie klas Frameworka
Forum PHP.pl > Forum > PHP
pitbull82
Witam

Tworzę właśnie pewien portal, a przy okazji framework, który ma mi się przydać do dalszych zastosowań. Cały framework ma zastanowić zamkniętą skrzynkę klas w których nie będzie się nic edytowało, a ewentualną zmianę/rozszerzenie funkcjonalności będzie się osiągało poprzez dziedziczenie z klas bazowych/tworzenie nowych klas. Jak na razie wykombinowałem to tak, ze mam katalog system w którym znajduje się cały framework i katalog application/main w którym znajduje się kopia wszystkich klas frameworka i każda klasa (o ile nie wymaga zmiany funkcjonalności) wygląda tak:

Kod
namespace WebApp;

class JakasKlasa extends \Framework\JakasKlasa {
}


Jednocześnie we wszystkich plikach frameworka nie odwołuję się nigdy do przestrzeni Framework tylko zawsze do przestrzeni WebApp dzięki czemu osiągam to co chciałem - dowolna klasa frameworka może zostać w danej aplikacji zmieniona na inną czy to przez dziedziczenie czy to przez napisanie klasy od podstaw.

Minusy tego rozwiązania:

- wymuszam sytuację, że rozszerzenie frameworka musi znajdować się zawsze w przestrzeni WebApp (chociaż to chyba można by ruszyć poprzez ładowanie przestrzeni z plików konfiguracyjnych)
- wymuszam sytuację, że dla każdej klasy frameworka musi istnieć klasa w przestrzeni WebApp nawet jeśli nic szczególnego się w niej nie dzieje tzn. jeśli dziedziczy ona tylko po klasie z framworka

Pytanie do Was - co sądzicie o opisanym rozwiązaniu i czy można się jakoś prosto pozbyć drugiego minusa tzn. tworzenia w zasadzie pustych plików które nic nie wnoszą?

Pozdrawiam
Crozin
Beznadzieje i kompletnie nie warte rozpatrzenia rozwiązanie.

1. Co jeżeli będzie chciał mieć kilka różnych implementacji tego samego "bytu"?
2. Dziedziczenie nie jest mechanizmem do zachowywania spójności obiektów - od tego są interfejsy.
3. Słowo klucz: Dependency Injection Container. To rozwiązanie sprawdziło się już w Javie, od względnie niedawna spopularyzowane w PHP sprawdza się i w nim - zresztą język nie ma tu wiele do rzeczy. Masz nawet gotową, całkiem dobrą implementację udostępnioną w ramach projektu Symfony Components.
4. Co właściwie skłoniło Cię do pisania własnego FW?
pitbull82
1. Możesz chociażby w konstruktorze klasy aplikacji stworzyć obiekt danej implementacji i wszystkie metody głównej klasy mogą stanowić proxy dla metod tego obiektu

2. Tutaj nie do końca chodzi o spójność obiektów, bo z założenia 90% klas nie będzie ani rozszerzanych ani nie będzie zmieniana implementacja żadnych metod tej klasy

3. Jeśli idea jest taka jak http://rekurencja.pl/php/symfony2/kontener...-injection.html to nie bardzo mi się podoba takie rozwiązanie, bo zakładając że front controller ma być również częścią samego framworka w pliku index.php musiałbym utworzyć wszystkie obiekty (nawet jeśli nie zostaną ostatecznie wykorzystane) i przekazać je do front controllera, chyba że źle zrozumiałem ideę

4. Własne rozwiązania są moim zdaniem zawsze najlepsze, bo dają 100% elastyczności, rozwijanie kodu jest znacznie prostsze i zazwyczaj są lekkie bo nie muszą zakładać upodobań dużej grupy programistów i wszystkich możliwych sposobów wykorzystania
Crozin
1. I chcąc dodać nową implementację musiałbym i tak modyfikować kod owego kontrolera. Zresztą jeżeli na etapie projektowania już wychodzi Ci, że musisz tworzyć obejścia przy tak błahym problemie to pierwszy znak, że coś zrypałeś.
2. Ale pozostaje te 10% i wypadałoby umożliwić jakiś ludzki mechanizm zarządzania tym.
3. Tak, źle zrozumiałeś ideę. Tutaj możesz przeczytać o ogólnej idei: http://symfony.com/doc/2.0/book/service_container.html
4. Najpierw piszesz o 90% stałego kodu, teraz o elastyczności. Z reguły są proste, a proste nie może być elastyczne. Następnie piszesz, że są ociężałe bo starają się obsłużyć wszystkie możliwe sposoby wykorzystania, czyli na dobrą sprawę są elastyczne. Zresztą lekkość nie ma za wiele wspólnego z obszernością kodu. Trochę nie trzymają się Twoje poglądy kupy. Aaa... i w przypadku własnego kodu dolicz kilkaset / klika tysięcy godzin na napisanie tego.
Zyx
Pomysł jest kompletnie bez sensu i zaprzecza zarówno idei obiektówki, jak i zasadom projektowania za ich pomocą.

Przedkładaj kompozycję nad dziedziczenie
Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Powinny zależeć od abstrakcji[/b] - Zasada Odwrócenia Zależności

Fajnie, kod frameworka odwołuje się do klas w przestrzeni [i]Webapp
, tylko co z tego, kiedy później stwierdzasz: dobra, trzeba napisać testy, a w tych testach nie chcę startować 3/4 systemu, by przetestować 100-linijkową klasę. Przecież jej testowany obiekt może pracować na atrapach. Ups, tylko jak te atrapy tam wstawić, kiedy twórca klasy za bardzo zapatrzył się w dziedziczenie swoją przestrzeń Webapp i tworzy zależności zaszyte na sztywno? Idźmy dalej. Mamy sobie interfejs, implementuje go 5 klas dostarczających różnych wariantów implementacji... zaraz, jakie 5 klas? W Twoim rozwiązaniu mamy klasę bazową i dziedziczenie po Webapp, czyli lipa niemająca nic wspólnego z obiektówką. Jak chcesz dać możliwość rozszerzania, to robisz interfejs i mówisz: "jak chcesz mieć własną implementację, zaimplementuj ten interfejs i będzie OK". Już pomijam fakt, że ponieważ "wszystko odwołuje się do Webapp", nie ma jak nawet powiedzieć systemowi, który wariant wybieramy.

A teraz najlepsze. Opracowujemy zestaw wspólnych komponentów dla wielu projektów. I znowu zaczyna się science-fiction, bo framework mówi: "Nie będziesz miał przestrzeni cudzych przede mną". I o komponentach wielokrotnego użytku możesz zapomnieć.

Cytat
4. Własne rozwiązania są moim zdaniem zawsze najlepsze, bo dają 100% elastyczności, rozwijanie kodu jest znacznie prostsze i zazwyczaj są lekkie bo nie muszą zakładać upodobań dużej grupy programistów i wszystkich możliwych sposobów wykorzystania


Wszystko to prawda... pod warunkiem, że programista zna się na tym, co robi i umie prawidłowo korzystać z posiadanych narzędzi. Ponieważ korzystasz z komputera, to na pewno gdzieś mieszkasz i na pewno masz do tego miejsca zamieszkania jakieś drzwi. Drzwi zrobić każdy głupi potrafi z desek przy pomocy gwoździ i młotka, tylko czy powierzyłbyś im później bezpieczeństwo swojego mieszkania? Czy uważasz, że takie zbite z desek drzwi są w stanie lepiej zabezpieczyć Twoje mieszkanie, niż drzwi wyprodukowane w specjalistycznej firmie? Niestety, ale chęci do posiadania własnych drzwi to nie wszystko. 99,9% programistów nie ma wystarczających umiejętności i talentu do projektowania (o czasie już nie wspominając), by osiągnąć poziom choćby w połowie tak dobry, jak gotowe rozwiązania.

A Tobie brakuje rzeczy wręcz elementarnej: umiejętności krytycznego spojrzenia na swój pomysł. Jeśli uważasz, że wymyśliłeś coś genialnego, to weź sobie jakieś tabletki na ból głowy, prześpij się i wróć do dyskusji jutro rano. Nie chodzi nawet o to, że zaprzecza on temu, co ludzkość wypracowała przez kilkadziesiąt lat używania obiektówki, ale o to, że uważasz go za coś doskonałego, a to jest najprostsza droga do katastrofy. Nie ma rozwiązań doskonałych. Praktycznie zawsze jest to coś kosztem czegoś. Jeśli nie potrafisz powiedzieć, co tracisz, a co zyskujesz, to prędzej czy później to się na Tobie zemści. Jeśli nie potrafisz dostrzec wad obecnego rozwiązania, czeka je stagnacja, bo spoczniesz na laurach.
pitbull82
Co do dziedziczenia to akurat tylko przykład jak wygląda dana klasa jeśli faktycznie nie zmienia funkcjonalności bazowej klasy frameworka. Nic nie stoi na przeszkodzie żeby napisać klasę zupełnie od nowa bez dziedziczenia po klasie bazowej implementując tylko odpowiedni interfejs.

Oczywiście nie twierdzę, że moje rozwiązanie jest idealne i nie ma żadnego lepszego, ale czy w takim razie możecie mi pokazać framework, który zakłada że każda dowolna klasa frameworka może zostać zmieniona na własną oczywiście bez edycji pliku tej klasy tylko stworzenie klasy w oddzielnym pliku?
wiiir
Cytat(pitbull82 @ 6.04.2011, 07:53:23 ) *
Oczywiście nie twierdzę, że moje rozwiązanie jest idealne i nie ma żadnego lepszego, ale czy w takim razie możecie mi pokazać framework, który zakłada że każda dowolna klasa frameworka może zostać zmieniona na własną oczywiście bez edycji pliku tej klasy tylko stworzenie klasy w oddzielnym pliku?


Powiem tak, jestem mega ciekawy jak to ma dzialac, twoje zalozenia od samego poczatku sa malo logiczne w rozumieniu obiektowosci..

Nie lepiej korzystac z gotowych rozwiazan?? Wez w oboroty zend-a, bardzo duza biblioteka ktora na pewno bedzie miala wieksze możliwosci niz twoj framework. Co do tego ze framework wlasny jest zawsze lepszy to przyklad zyx-a jest trafiony w 10
Crozin
Na chwilę obecną chyba jedynie Symfony2 (w PHP) realizuje to we wskazany przeze mnie sposób. Bardziej "dojrzały" przykład można zobaczyć chociażby w Springu (Java).
dariuszp
Podstawiasz sobie nogę już w samych założeniach wg mnie. Sam budując system trzymałem się zasady KISS i sprawdza się ona świetnie.
Jeżeli mówisz o samym ładowaniu klas to czemu nie skorzystać z PSR-0 ? Sam wykorzystuję tę propozycję z małymi modyfikacjami i sprawdza się świetnie.
http://groups.google.com/group/php-standar...-final-proposal

Obrałem sobie przestrzeń App na moją aplikację i napisałem autoloader (spl_autoload_register zobacz czy jakoś tak). Chcę odpalić moduł ?

  1. $module = new Module\Shop\WebShop($this->App);


chcę odpalić część administracyjną modułu ?

  1. $module = new Module\Shop\AdminShop($this->App);


Trzeba mi bibliotekę "strzelGola" od Batmana ?

  1. $strzelGola = new \Batman\StrzelGola();


itp itd etc.

Autoloader działa gdy tworzysz nowy obiekt, działa gdy obiekt rozszerza inny obiekt. Biblioteki są zawsze ułożone wg schematu: katalog_z_bibliotekami/dostawca/nazwa.php za wyjątkiem modułów na który jest osobny katalog (co autoloader też pokrywa). Bez problemu działa to z takimi funkcjami jak class_exists itp więc w moim frameworku w ogóle nie używam include/require ręcznie.
Do tego o ile się orientuję, nazewnictwo PSR-0 staje się coraz bardziej popularne co właściwie gwarantuje mi że w przyszłości będę mógł wykorzystywać biblioteki od różnych dostawców i frameworków nie martwiąc się w ogóle o kompatybilność itp.

Mało tego, w ten sposób mogę sobie OD TAK załadować dowolny moduł, bibliotekę itp i należycie przetestować.

Radzę się zapoznać i samemu ocenić.
Pozdrawiam
Crozin
@dariuszp: Tylko co to ma do tematu?
dariuszp
+ nie trzeba robić jakiś pustych klas bóg wie po co i wprowadzać bałaganu
+ nie jesteś ograniczony do jednej przestrzeni nazw. Jak bym tak zarządzał klasami jak autor to przy dzisiejszym rozmiarze mojego FW pewnie bym już nie wiedział co się z czym je biorąc pod uwagę jak się rozrosła
+ nie ma problemów z wyodrębnianiem fragmentów aplikacji i ich testowaniu (z automatu)
+ mam zagwarantowane to że łatwo dorzucę zewnętrzny kod który stosuje się do PSR-0 a tego coraz więcej.
+ nie muszę bezsensownie dziedziczyć po klasie X jeżeli nie ma ku temu faktycznej potrzeby
+ operując przestrzeniami nazw z głową i "sposobem" możemy w dowolnym momencie wymienić dowolny element aplikacji bez jakiś wygibasów

Dlatego mówię że odgórne założenie jest kiepskie bo sprawia więcej problemów jak rozwiązuje. Chyba że czegoś nie załapałem.
Crozin
@dariuszp: Wszystko co napisałeś jest ok. Ogólnie PSR-0 jest OK. Tylko to nie ma nic do tematu bo jest on o tym jak "ogarnąć" wszystkie obiekty, a nie jakiej konwencji nazewnictwa i autoloadera używać...
ADeM
Ja tylko chciałem wtrącić, że o ile się nie mylę, to w Kohanie również: "każda dowolna klasa frameworka może zostać zmieniona na własną oczywiście bez edycji pliku tej klasy tylko stworzenie klasy w oddzielnym pliku".
dariuszp
@Crozin, no OK OK, po prostu chciałem zwrócić uwagę na to że wg mojej skromnej opinii autor sztucznie stworzył sobie problem który teraz stara się rozwiązać. Jeżeli założenie jest nieciekawe to rozwiązanie też do dobrych należeć nie będzie. No ale się nie wykłócam. Pozdrawiam.
pitbull82
@dariuszp - autoloader naturalnie mógłby ładować plik z danego katalogu, ale chyba warunkiem byłoby że "nadpisane" klasy byłyby w tej samej przestrzeni nazw co klasy frameworka no i dodatkowo nie byłoby możliwe ewentualne wykonanie dziedziczenia, czyli gdyby chciało się dopisać tylko jedną metodę, trzeba by było całą klasę od podstaw tworzyć.
dariuszp
No tak tylko zauważ że ładując klasę taką jak np \Application\Db\Mysql można spokojnie np załadować \Application\Db\SuperMysql która dziedziczy po \Application\Db\Mysql (ale nie musi). Autoloader załatwi Ci ładowanie rodziców.
Jak chcesz podmieniać "w locie" klasę na zasadzie wrzucam nowy plik z klasą == jest on ładowany zamiast oryginału" to możesz to spokojnie osiągnąć na 1001 sposobów. Przykładowo tworzysz sobie taki twór:
\Overwrite\Application\Db\Mysql i autloader patrzy za nim ilekroć próbuje załadować jakiś plik. Jeżeli nadpisywanie ma być wielopoziomowe (znaczy klasa Test może być nadpisana przez SuperTest a SuperTest przez SuperTest2 itp...) to można po prostu operować przestrzeniami nazw jak np \ext1\Application\Db\Mysql, \ext2\Application\Db\Mysql. Można zrobić mały "skaner" który Ci wyłapie wszystkie klasy które np implementują interfejs "ZrodloDanych" albo jak sobie takowy nazwiesz, zrobi Ci listę i np zmienisz dostawcę bo masz:
\Application\Db\MySql
\Batman\Db\MySql
\Dariuszp\Db\MySql

I tak piszesz sobie skrypt który skanuje katalog z bibliotekami i robi Ci grupy klas implementujących ten sam interfejs. Tworzy Ci okienko w którym możesz sobie po prostu przełączyć dostawcę i klasę. Po zapisaniu zmian, podmieniany jest jakiś plik konfiguracyjny z którego korzysta autoloader.
I w ten sposób zyskujesz elastyczność, możliwość podmiany dowolnego elementu itp.
I tak jeżeli bazą jest przestrzeń Application to ładując Applicaton\Db\Mysql, autloader odczytuje z pliku konfiguracyjnego info że jest on zamieniony przez \Batman\Db\Mysql i tę klasę załaduje.

Sytuacja jest o tyle fajna że na dobrą sprawę, \Batman\Db\Mysql może ale nie musi dziedziczyć po oryginale. Załadowanie w przypadku dziedziczenia zapewnia Ci autloader więc tutaj dużych problemów nie ma. Nawet jak system padnie z jakiegoś powodu, można usunąć albo ręcznie zmienić plik konfiguracyjny bez usuwania/przenoszenia plików klas żeby przywrócić np oryginał. A implementować interfejs "ZrodloDanych" może każda klasa. Mało tego, jedne klasa może implementować kilka interfejsów więc zyskujemy sytuację gdzie klasa X może być wykorzystana niezależnie w 2 miejscach w systemie.

Nie zgodzę się też że był by problem z dziedziczeniem. \Batman\Db\Mysql może być samodzielną klasą, może też dziedziczyć po \Application\Db\Mysql. Jak mówiłem, nie widzę też powodu by elementy systemu należały do jednej przestrzeni nazw.

Z debugowaniem też nie ma problemów bo backtrace zawsze Ci pokaże plik i nazwę klasy->metody w jakiej masz problem niezależnie od tego czy użytkownik wpisał sobie \Application\Db\Mysql czy \Batman\Db\Mysql.

Ogólnie można myśleć, bawić się, kombinować itp.

PS: Akurat mechanizm w którym w systemie wyłapuje wszystkie interfejsy dla klas a potem "skanuje" cały system robiąc listę bibliotek pogrupowanych wg implementowanych przez nich interfejsów jest częścią mojej pracy magisterskiej. Jej tematem jest modułowy system CMS pod którym kryje się zwykły mechanizm modułów do rozszerzania możliwości CMS'a, mechanizm pluginów pozwalających "podczepić się" pod jakieś akcje w systemie (np User->add możę odpalić Statistic->regStat a Statistic->regStat może odpalić Image->drawGraph) i ten który w skrócie opisałem wyżej, pozwalający na podmianę właściwie dowolnej klasy w systemie na jej odpowiednik wykorzystując autoloader i plik konfiguracyjny do niego generowany przez panel administracyjny aplikacji.
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.