Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: Rejestr do oceny
Forum PHP.pl > Forum > PHP > Object-oriented programming
Black-Berry
*Plik Bootstrap* Każdemu obiektowi który ma mieć zakres globalny przekazujemy w konstruktorze context
  1. <?php
  2. $context = new Context();
  3.    $config = new Config($context);
  4.    $log = new Log($context);
  5.    $dbDriver = new Db_Driver($context);
  6.    $session = new Session($context);
  7.    $language = new Language($context);
  8.    $user = new User($context);
  9.    $structure = new Structure($context);
  10.    $router = new Router($context);
  11.    $buffer = new Buffer($context);
  12. ?>


Każdy obiekt globalny dziedziczy po klasie System_Object
  1. <?php
  2.    class System_Object
  3.    {
  4.        public $context = array();
  5.        
  6.        public function __construct(Context $context)
  7.        {
  8.            $this->context = $context->getRegistry();
  9.            $context->register($this);
  10.        }
  11.        
  12.        public function __get($key)
  13.        {
  14.            if(isset($this->context[$key])) {
  15.                return $this->context[$key];
  16.            }
  17.        }
  18.    }
  19. ?>


Obiekt Kontext
  1. <?php
  2.    class Context
  3.    {
  4.        private $registry = array();
  5.    
  6.        public function __get($key)
  7.        {
  8.            if(isset($this->registry[$key])) {
  9.                return $this->registry[$key];
  10.            }
  11.        }
  12.        
  13.        public function register(System_Object $object)
  14.        {
  15.            $key = System_Strings::variableNotation(get_class($object));
  16.            $this->registry[$key] = $object;
  17.        }
  18.        
  19.        public function getRegistry()
  20.        {
  21.            return $this->registry;
  22.        }
  23.    }
  24.    
  25. ?>


Przykłądowa klasa. Wszystkie inne na podobnej zasadzie. Nie ma żadnych setterów, getterów, wszystko bez zbędnych kodów.
  1. <?php
  2.    class Config extends System_Object
  3.    {
  4.        public $test = 'Wartość testowa';
  5.    
  6.        public function __construct(Context $context)
  7.        {
  8.            parent::__construct($context);
  9.        }
  10.    }  
  11. ?>


sposób użycia
  1. <?php
  2.    print $user->config->test;  // jak widac obiekt wyswietla zmienna z innego obiektu
  3.    $log->config->test = 'foo'; // a zupelnie inny obiekt modyfikuje ta wartosc
  4.    print $user->config->test; // no i wartosc ulega zmienie tak jakby byla globalna
  5. ?>


Moim zdaniem to musi być ostateczne rozwiązanie problemu globalsów. Bardzo proszę o dyskusję

Czy singletony zamiast tego byłyby wydajniejsze ? Podobno singletony to zło. Ja już zgupłem do reszty @_@
empathon
Masz racje. Singleton zazwyczaj oznacza źle zaprojektowaną aplikacje: to taki GOTO OOP.
Najważniejsze jest zaplanowanie odpowiedniego workflow obiektów.

Rozwiązanie z przekazywaniem obiektów jest dobre.
Dodałbym jeszcze interface dla każdej z klas i zamiast bezpośredniego dostępu przez get i sety (private $contex) odpowiednie metody. Nie wszędzie potrzebujesz automagiczności a dostęp przez metody daje dodatowe możliwości nadpisania po drodze przez dziecko (tak samo nie wymagaj klasy ale instanceof).
Black-Berry
a co jest szybsze w php? singletony czy trzymanie referencji do innych klas?

Cytat(empathon @ 3.11.2008, 17:45:03 ) *
(tak samo nie wymagaj klasy ale instanceof).


Czym się różni instanceof od wymagania klasy ?
empathon
Oczywiście korzystanie z referencji jest szybsze.
Nie musisz pobierać instancji obiektu ale masz do niego bezpośredni "skrót".

Typując parametr po nazwie klasy pozwalasz przekazać tylko obiekty danej klasy. Używając instanceof w połączeniu z interface zapewniasz sobie spójne API jeśli chciałbyś kiedyś podmienić którąś z klas. Np.: ? Request -> (WebRequest/ConsoleRequest).
To właśnie jest istotą OOP. Obiekty mają pracować ze sobą a nie być połączone na sztywno.
destroyerr
empathon mylisz się. Type hinting właśnie służy do tego co opisałeś. Jeśli zdefiniujesz interfejs i w klasach będziesz go implementował to spokojnie to zadziała. Może jak zobaczysz kod to sobie przypomnisz bo na pewno o tym wiesz:
  1. <?php
  2. interface Request
  3. {
  4. }
  5.  
  6. class Context
  7. {
  8.    public function setRequest(Request $request)
  9.    {
  10.    }
  11. }
  12.  
  13. class WebRequest implements Request
  14. {
  15. }
  16.  
  17. class ConsoleRequest implements Request
  18. {
  19. }
  20.  
  21. $context = new Context();
  22. $context->setRequest(new WebRequest());
  23. $context->setRequest(new ConsoleRequest());
  24. ?>


Co do tematu to od siebie dodam, że warto zainteresować się wzorcem http://martinfowler.com/articles/injection.html W kontekście php najlepszą prezentacją jaką widziałem jest ta. A w niej 3 kluczowe linki do projektów:
http://phpcrafty.sourceforge.net/
http://garden.tigris.org
http://www.stubbles.net/wiki/Docs/IOC
pp-layouts
Witam

Znalazłem inne rozwiązanie problemu globali. Moje podejście jest minimalistyczne. Minimum pisania, maximum czytelności, pełna funkcjonalność. Używam statycznych singletonów, najprościej przedstawię to na hiper-uproszczonym przykładzie. Dodaję sobie do aplikacji klasę app, która jak sama nazwa wskazuje, trzyma dane aplikacji...

Kod
class app {

  public static $qa;

  ...

}




Zmienna app::$qa przechowuje mi np informację, czy aplikacja jest odpalona w trybie testowym. W dowolnym miejscu kodu sprawdzam sobie...


Kod
if (app::$qa) ...


...i pozamiatane.



Stanu aplikacji nie muszę "przekazywać" do modułów. Mogą sobie go sprawdzać od ręki, w każdej chwili, mogą go też zmieniać. W zasadzie moja klasa app jest też pewnym rodzajem rejestru, ale prostszym. Dodatkowo posiada pewną kolosalną przewagę: jeśli używasz Eclipse, ZendStudio, albo podobnego cuda z opcją CodeAssist lub AutoComplete - wszystkie klucze tego rejestru będą się "wklepywać" automatycznie po wpisaniu 1, góra 3 pierwszych liter. Oczywiście klasa app w rzeczywistości robi dużo więcej poza samym trzymaniem stanu. Najpierw inicjuje się, inicjuje całą aplikacje, wgrywa pliki, otwiera bazę, no wszystko co tam jest potrzebne na dzień dobry i nie podpada pod specjalistyczne moduły.

Ciekawi mnie, jakie moje rozwiązanie ma wady i dlaczego kurcze nikt takiego myku nie używa?
Black-Berry
Cytat(destroyerr @ 3.11.2008, 21:26:22 ) *
(..)Co do tematu to od siebie dodam, że warto zainteresować się wzorcem dependency injection (..)
Dzięki za linki. Trochę poczytałem i wydaje mi się że to co ja napisałem to właśnie ten wzorzec tylko całkowicie zautomatyzowany. Czy się nie mylę?


Cytat(pp-layouts @ 3.11.2008, 22:53:41 ) *
Ciekawi mnie, jakie moje rozwiązanie ma wady i dlaczego kurcze nikt takiego myku nie używa?
Myślę że temu, że to przeczy zasadom obiektowości. Poza tym większość ludzi używa singletonów na podobnej zasadzie a ponoć są szybsze niż klasy statyczne ale mogę się mylić.

Moje rozwiązanie różni się tym od Twojego, że (na podstawie tego co mi napisał empathon) jest o wiele szybsze w działaniu. A poza tym przy lekkiej modyfikacji możnaby do różnych klas dodawać różne konteksty. Myślę, że moje rozwiązanie nadaje się tylko dla leniwych ale ja jestem raczej leniwy więc mi leży.
l0ud
Black-Berry, nie wiem czy do końca zrozumiałem Twój kod, ale imho trochę go przekombinowałeś. Jeżeli coś palnąłem, to mnie popraw winksmiley.jpg


  1. <?php
  2. $registry = $context->getRegistry();
  3.           while ($object = current($registry)) {
  4.               $this->context[key($registry)] = $object;
  5.               next($registry);
  6.           }
  7. ?>


Czyli z obiektu $context pobierasz listę elementów i w pętli (no właśnie - po co w pętli? Zwykłe przypisanie nie zadziała?) ładujesz wszystkie obiekty do tablicy $this->context, do której upraszczasz sobie później dostęp przez gettera. A co jeżeli po tym obiekcie, doda się następny? Nie będziesz miał do niego dostępu - lista elementów będzie już stara.
Poza tym, dostęp do obiektów nie powinien być taki prosty. W ten sposób mieszasz elementy klasy z globalnymi. Poza tym pozwalasz na takie cuda:
  1. <?php
  2. $buffer->router->structure->user->language->session->dbDriver...
  3. ?>


Co ma obiekt $buffer, do bazy danych? winksmiley.jpg


Ja u siebie robię to tak: mam jeden główny obiekt $ffCore który zajmuje się wczytywaniem innych i 'komunikacją' między nimi.

Początek przykładowej klasy wygląda tak:
  1. <?php
  2. class config implements createdByCore {
  3.    
  4.    private $db;
  5.    private $configArray = array();
  6.    
  7.    public function __construct(ffCore $ffCore) {
  8.        $this->db = $ffCore->database;
  9.        $this->loadConfig();
  10.    }
  11. ?>


Utworzenie jej obiektu:
  1. <?php
  2. $config = $ffCore->config;
  3. ?>

Główny kontroler sprawdza, czy zawiera już instancję obiektu o takiej nazwie. Jeżeli nie, tworzy ją.

W przy tworzeniu, '$ffCore' przekazuje swoją instancję w konstruktorze i umożliwia mu stworzenie aliasów do innych obiektów, które będą potrzebne. A jeżeli jakiś obiekt pojawi się później, zawsze mogę utworzyć alias do samego $ffCore, czego jednak staram się unikać.
Black-Berry
Dzxięki L0ud. Wszystko o czym mówisz jest całkowitą prawdą. Pętla była pozostałością po czymś co przestało być potrzebne i teraz już ją wywaliłem. Muszę to przemyśleć jeszcze raz bo faktycznie starsze elementy nie mają dostępu do nowych. Cuda takie jak to:

  1. <?php
  2. $buffer->router->structure->user->language->session->dbDriver...
  3. ?>

chyba nie będą takim problemem bo każdy kto trochę myśli to zamiast tego napisze:
  1. <?php
  2. $buffer->dbDriver
  3. ?>

No i czasem buffer też potrzebuje dbDrivera jeśli np będzie chciał cach'a całej strony zapisać do bazy danych. Mówi się dużo na temat ukrywania niektórych obiektów ale piszę już ponad rok i zawsze wychodza takie sytuacje w których jeden obiekt wymaga takiego o którym wcześniej nie pomyslałem i wtedy znowu trzeba przerabiać konstruktor, tworzyć nowego settera, gettera. Trochę się mi to już znudziło i chcę to maksymalnie zautomatyzować.

Edit: Tak po chwili zastanowienia... Czy obiektr language potrzebuje obiektu user? Albo czy config potrzebuje dbDriver? Myślę, że nie i takie hierarchiczne poukładanie jest pewną formą wyłączenia zbędnych obiektów z obiegu. Idąc w górę drzewka zawsze znajdę powiązanie. Np language potrzebuje session. Idąc w dół nie widzę takich zależnosci ale może ktoś się do tego ustosunkuje.

P.S. Dlaczego masz takie dziwne prefiksy klas 'ff' smile.gif ?
Crozin
Cytat
P.S. Dlaczego masz takie dziwne prefiksy klas 'ff' ?
PHP nie obsługuje jeszcze przestrzeni nazw (namespace) i trzeba sobie jakoś radzić z unikalnym nazewnictwem klas. "ff" jest zapewne skrótem od nazwy projektu, na przykład nazwa sfRequest oznacza Request z projektu symfony (działa to na dokładnie takiej samej zasadzie jak prefiksy w nazwach tabel w bazie danych).
Black-Berry
No tak ale osobiście źle mi się to czyta. Może lepiej tak jak w zendzie. Jeśli masz framework o nazwie Ziuziu to klasy piszesz
  • Ziuziu_Core
  • Ziuziu_Registry
  • Ziuziu_Config
pozatym nazwy klas zawsze zaczynasz od dużej litery i wiesz co jest klasą a co czymś innym. To szczegół ale moze komuś ułatwi analizę.
l0ud
'ff' to skrót od nazwy projektu. Akurat nazewnictwo to sprawa indywidualna winksmiley.jpg Ja do nazywania czegokolwiek używam mixedCase, a Ci wygodniej inaczej - coś jakby CamelCase ze znakami podkreślenia.

Wracając do tematu, pokazałem swój sposób rozwiązania tego problemu. Po prostu w konstruktorze odbieram $ffCore i z niego robię sobie aliasy do wymaganych mi obiektów. Dzięki temu później w klasie nie mam dostępu do niepotrzebnych obiektów i od razu widzę jakie ma zależności. Gettera mogę wykorzystać do czegoś innego i nie mam problemów typu brak hermetyczności winksmiley.jpg

Pozdrawiam
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.