Kiedy używać klas statycznych, a kiedy utworzonych z instancji

170

PHP to mój pierwszy język programowania. Nie mogę się do końca ogarnąć, kiedy używać klas statycznych, a kiedy obiektów utworzonych.

Zdaję sobie sprawę, że możesz powielać i klonować obiekty. Jednak przez cały mój czas używania php każdy obiekt lub funkcja zawsze kończyła się jako pojedyncza wartość zwracana (tablica, ciąg znaków, int) lub wartość void.

Rozumiem pojęcia z książek, takie jak klasa postaci z gier wideo. zduplikować obiekt samochodu i uczynić nowy czerwony , to wszystko ma sens, ale to, co nie jest, to jego zastosowanie w php i aplikacjach internetowych.

Prosty przykład. Blog. Jakie obiekty bloga najlepiej byłoby zaimplementować jako obiekty statyczne lub utworzone na podstawie instancji? Klasa DB? Dlaczego po prostu nie utworzyć instancji obiektu db w zakresie globalnym? Dlaczego zamiast tego uczynić każdy obiekt statycznym? A co z wydajnością?

Czy to tylko styl? Czy istnieje właściwy sposób na zrobienie tego?

user73119
źródło

Odpowiedzi:

123

To dość interesujące pytanie - a odpowiedzi mogą też stać się interesujące ^^

Najprostszym sposobem rozważenia rzeczy może być:

  • użyj klasy instancjonowanej, w której każdy obiekt ma własne dane (np. użytkownik ma nazwę)
  • używaj klasy statycznej, gdy jest to tylko narzędzie, które działa na innych rzeczach (na przykład konwerter składni kodu BB na HTML; nie ma własnego życia)

(Tak, przyznaję, naprawdę, bardzo uproszczone ...)

Jedną rzeczą w statycznych metodach / klasach jest to, że nie ułatwiają testów jednostkowych (przynajmniej w PHP, ale prawdopodobnie także w innych językach).

Inną rzeczą związaną z danymi statycznymi jest to, że w twoim programie istnieje tylko jedna ich instancja: jeśli ustawisz gdzieś MyClass :: $ myData na jakąś wartość, będzie ona miała tę wartość i tylko ją, w każdym miejscu - Mówiąc o użytkowniku, mógłbyś mieć tylko jednego użytkownika - co nie jest takie wspaniałe, prawda?

Co mogę powiedzieć o systemie blogowym? Wydaje mi się, że niewiele można napisać jako statyczne; może klasa dostępu do bazy danych, ale prawdopodobnie nie, w końcu ^^

Pascal MARTIN
źródło
Więc zasadniczo używaj obiektów podczas pracy z unikalnymi rzeczami, takimi jak zdjęcie, użytkownik, post itp. I używaj statycznego, gdy jest to przeznaczone do ogólnych rzeczy?
Robert Rocha
1
mówiąc o użytkowniku, na każde żądanie masz tylko jednego aktywnego użytkownika. więc zajęcie aktywnego użytkownika statycznego żądania miałoby sens
My1
69

Główne dwa powody przeciwko stosowaniu metod statycznych to:

  • kod przy użyciu metod statycznych jest trudny do przetestowania
  • kod przy użyciu metod statycznych jest trudny do rozszerzenia

Posiadanie statycznego wywołania metody wewnątrz innej metody jest w rzeczywistości gorsze niż importowanie zmiennej globalnej. W PHP klasy są symbolami globalnymi, więc za każdym razem, gdy wywołujesz metodę statyczną, polegasz na symbolu globalnym (nazwie klasy). Tak jest w przypadku, gdy globalność jest zła. Miałem problemy z takim podejściem z jakimś komponentem Zend Framework. Istnieją klasy, które używają statycznych wywołań metod (fabryk) do budowania obiektów. Nie mogłem dostarczyć innej fabryki do tej instancji w celu zwrotu dostosowanego obiektu. Rozwiązaniem tego problemu jest używanie tylko instancji i metod instancji oraz wymuszanie singletonów i tym podobnych na początku programu.

Miško Hevery , który pracuje jako Agile Coach w Google, ma ciekawą teorię, a raczej radzi, że powinniśmy oddzielić czas powstania obiektu od momentu jego użycia. Tak więc cykl życia programu jest podzielony na dwie części. Pierwsza część ( main()powiedzmy metoda), która zajmuje się całym okablowaniem obiektów w Twojej aplikacji oraz częścią, która wykonuje rzeczywistą pracę.

Więc zamiast mieć:

class HttpClient
{
    public function request()
    {
        return HttpResponse::build();
    }
}

Powinniśmy raczej zrobić:

class HttpClient
{
    private $httpResponseFactory;

    public function __construct($httpResponseFactory)
    {
        $this->httpResponseFactory = $httpResponseFactory;
    }

    public function request()
    {
        return $this->httpResponseFactory->build();
    }
}

Następnie na stronie indeksu / głównej zrobilibyśmy (jest to krok okablowania obiektu lub czas na utworzenie wykresu instancji, które mają być używane przez program):

$httpResponseFactory = new HttpResponseFactory;
$httpClient          = new HttpClient($httpResponseFactory);
$httpResponse        = $httpClient->request();

Głównym pomysłem jest oddzielenie zależności od twoich klas. W ten sposób kod jest znacznie bardziej rozszerzalny i, co dla mnie najważniejsze, testowalny. Dlaczego ważniejsze jest, aby być testowalnym? Ponieważ nie zawsze piszę kod biblioteki, więc rozszerzalność nie jest tak ważna, ale testowalność jest ważna podczas refaktoryzacji. W każdym razie, testowalny kod zazwyczaj daje rozszerzalny kod, więc tak naprawdę nie jest to sytuacja „albo-albo”.

Miško Hevery dokonuje również wyraźnego rozróżnienia między singletonami i singletonami (z wielką literą S lub bez niej). Różnica jest bardzo prosta. Singletony z małymi literami „s” są wymuszane przez okablowanie w indeksie / głównym. Tworzysz obiekt klasy, która nie implementuje wzorca Singleton, i dbasz o to, aby przekazać to wystąpienie tylko do każdej innej instancji, która tego potrzebuje. Z drugiej strony Singleton, przez duże „S” jest implementacją klasycznego (anty) wzorca. Zasadniczo globalny w przebraniu, który nie ma większego zastosowania w świecie PHP. Do tej pory żadnego nie widziałem. Jeśli chcesz, aby jedno połączenie DB było używane przez wszystkie Twoje klasy, lepiej zrób to w ten sposób:

$db = new DbConnection;

$users    = new UserCollection($db);
$posts    = new PostCollection($db);
$comments = new CommentsCollection($db);

Robiąc powyższe, widać wyraźnie, że mamy singletona i mamy też fajny sposób na wprowadzenie makiety lub kodu pośredniczącego w naszych testach. Zaskakujące jest, jak testy jednostkowe prowadzą do lepszego projektu. Ale ma to duży sens, gdy myślisz, że testy zmuszają cię do przemyślenia sposobu, w jaki użyjesz tego kodu.

/**
 * An example of a test using PHPUnit. The point is to see how easy it is to
 * pass the UserCollection constructor an alternative implementation of
 * DbCollection.
 */
class UserCollection extends PHPUnit_Framework_TestCase
{
    public function testGetAllComments()
    {
        $mockedMethods = array('query');
        $dbMock = $this->getMock('DbConnection', $mockedMethods);
        $dbMock->expects($this->any())
               ->method('query')
               ->will($this->returnValue(array('John', 'George')));

        $userCollection = new UserCollection($dbMock);
        $allUsers       = $userCollection->getAll();

        $this->assertEquals(array('John', 'George'), $allUsers);
    }
}

Jedyną sytuacją, w której użyłbym (i użyłem ich do naśladowania obiektu prototypu JavaScript w PHP 5.3) statycznych elementów członkowskich, jest sytuacja, gdy wiem, że odpowiednie pole będzie miało tę samą wartość między instancjami. W tym momencie możesz użyć statycznej właściwości i być może pary statycznych metod pobierających / ustawiających. W każdym razie nie zapomnij dodać możliwości zastąpienia statycznego elementu członkowskiego elementem instancji. Na przykład Zend Framework używał statycznej właściwości w celu określenia nazwy klasy adaptera DB używanej w instancjach Zend_Db_Table. Minęło trochę czasu, odkąd ich używałem, więc może już nie mieć znaczenia, ale tak to pamiętam.

Metody statyczne, które nie obsługują właściwości statycznych, powinny być funkcjami. PHP ma funkcje i powinniśmy ich używać.

Ionuț G. Stan
źródło
1
@Ionut, ani trochę w to nie wątpię. Zdaję sobie sprawę, że to stanowisko / rola również ma wartość; jednak, podobnie jak wszystko, co dotyczy XP / Agile, ma naprawdę dziwaczną nazwę.
jason
2
Uważam, że „Dependency Injection” to nazbyt skomplikowany termin. DUŻO i dużo czytania na ten temat w całej SO i Google-land. Co do „singletonów”, oto coś, co naprawdę dało mi do myślenia: slideshare.net/go_oh/… Rozmowa o tym, dlaczego pojedyncze PHP naprawdę nie ma sensu. Mam nadzieję, że przyczynia się to trochę do starego pytania :)
dudewad
22

Więc w PHP statyczne można zastosować do funkcji lub zmiennych. Zmienne niestatyczne są powiązane z konkretną instancją klasy. Metody niestatyczne działają na instancji klasy. Stwórzmy więc klasę o nazwie BlogPost.

titlebyłby elementem niestatycznym. Zawiera tytuł tego wpisu na blogu. Możemy również mieć metodę o nazwie find_related(). Nie jest statyczny, ponieważ wymaga informacji z określonej instancji klasy wpisu na blogu.

Ta klasa wyglądałaby mniej więcej tak:

class blog_post {
    public $title;
    public $my_dao;

    public function find_related() {
        $this->my_dao->find_all_with_title_words($this->title);
    }
}

Z drugiej strony, używając funkcji statycznych, możesz napisać taką klasę:

class blog_post_helper {
    public static function find_related($blog_post) {
         // Do stuff.
    }
}

W tym przypadku, ponieważ funkcja jest statyczna i nie działa na żadnym konkretnym poście na blogu, musisz przekazać wpis na blogu jako argument.

Zasadniczo jest to kwestia projektowania zorientowanego obiektowo. Twoje klasy to rzeczowniki w twoim systemie, a funkcje, które na nie działają, to czasowniki. Funkcje statyczne są proceduralne. Obiekt funkcji przekazujesz jako argumenty.


Aktualizacja: Dodałbym również, że rzadko podejmuje się decyzję między metodami instancji a metodami statycznymi, a bardziej między używaniem klas i użyciem tablic asocjacyjnych. Na przykład w aplikacji do blogowania albo czytasz posty na blogu z bazy danych i konwertujesz je na obiekty, albo zostawiasz je w zestawie wyników i traktujesz je jako tablice asocjacyjne. Następnie piszesz funkcje, które jako argumenty przyjmują tablice asocjacyjne lub listy tablic asocjacyjnych.

W scenariuszu OO piszesz w swojej BlogPostklasie metody, które działają na poszczególnych postach, i piszesz metody statyczne, które działają na kolekcjach postów.

Rafe
źródło
4
Ta odpowiedź jest całkiem dobra, szczególnie w przypadku aktualizacji, którą zrobiłeś, ponieważ to jest powód, dla którego trafiłem na to pytanie. Rozumiem funkcjonalność „::” kontra „$ this”, ale kiedy dodasz do konta przykład, który podałeś, przedstawiający wyodrębnianie postów z bazy danych w tablicy asocjacyjnej. Dodaje to zupełnie nowy wymiar, to znaczy również w podstawowym tonie tego pytania.
Gerben Jacobs
To jedna z najlepszych praktycznych (w porównaniu do teoretycznych) odpowiedzi na to często zadawane pytanie dotyczące PHP, dodatek jest bardzo pomocny. Myślę, że jest to odpowiedź, której szuka wielu programistów PHP.
ajmedway
14

Czy to tylko styl?

Tak, długa droga. Możesz pisać doskonale dobre programy obiektowe bez używania statycznych elementów członkowskich. W rzeczywistości niektórzy ludzie argumentowaliby, że statyczne elementy są przede wszystkim nieczystością. Sugerowałbym, aby - jako początkujący w oop - starać się unikać statycznych członków razem. Zmusi cię to do pisania w stylu obiektowym, a nie proceduralnym .

troelskn
źródło
13

Mam tutaj inne podejście do większości odpowiedzi, zwłaszcza gdy używam PHP. Myślę, że wszystkie zajęcia powinny być statyczne, chyba że masz ku temu dobry powód. Oto niektóre z powodów „dlaczego nie”:

  • Potrzebujesz wielu instancji tej klasy
  • Twoja klasa musi zostać rozszerzona
  • Części twojego kodu nie mogą współużytkować zmiennych klas z żadnymi innymi częściami

Podam jeden przykład. Ponieważ każdy skrypt PHP generuje kod HTML, mój framework ma klasę pisarza HTML. Zapewnia to, że żadna inna klasa nie będzie próbowała pisać kodu HTML, ponieważ jest to wyspecjalizowane zadanie, które powinno być skoncentrowane w jednej klasie.

Zwykle użyjesz klasy html w następujący sposób:

html::set_attribute('class','myclass');
html::tag('div');
$str=html::get_buffer();

Za każdym razem, gdy wywoływana jest funkcja get_buffer (), resetuje ona wszystko, tak że następna klasa używająca programu zapisującego html zaczyna się w znanym stanie.

Wszystkie moje klasy statyczne mają funkcję init (), którą należy wywołać przed pierwszym użyciem tej klasy. Jest to bardziej umowne niż konieczność.

W tym przypadku alternatywa dla klasy statycznej jest nieuporządkowana. Nie chciałbyś, aby każda klasa, która musi napisać niewielką część kodu HTML, musiała zarządzać wystąpieniem programu piszącego HTML.

Teraz podam przykład, kiedy nie należy używać klas statycznych. Moja klasa formularza zarządza listą elementów formularza, takich jak dane wejściowe, listy rozwijane i nie tylko. Zwykle jest używany w następujący sposób:

$form = new form(stuff here);
$form->add(new text(stuff here));
$form->add(new submit(stuff here));
$form->render(); // Creates the entire form using the html class

Nie da się tego zrobić z klasami statycznymi, zwłaszcza biorąc pod uwagę, że niektórzy konstruktorzy każdej dodanej klasy wykonują dużo pracy. Ponadto łańcuch dziedziczenia dla wszystkich elementów jest dość złożony. Jest to więc wyraźny przykład, w którym nie należy używać klas statycznych.

Większość klas narzędziowych, takich jak te konwertujące / formatujące łańcuchy, jest dobrymi kandydatami do bycia klasą statyczną. Moja zasada jest prosta: wszystko jest statyczne w PHP, chyba że jest jeden powód, dla którego nie powinno.

JG Estiot
źródło
czy możesz napisać przykład poniższego punktu "Części twojego kodu nie mogą dzielić zmiennych klas z żadnymi innymi częściami" #JG Estiot
khurshed alam
10

„Posiadanie statycznego wywołania metody wewnątrz innej metody jest w rzeczywistości gorsze niż importowanie zmiennej globalnej”. (zdefiniuj „gorsze”) ... i „Metody statyczne, które nie mają do czynienia ze statycznymi właściwościami, powinny być funkcjami”.

Są to dość ogólne stwierdzenia. Jeśli mam zestaw funkcji powiązanych tematycznie, ale dane instancji są całkowicie nieodpowiednie, wolałbym, aby były zdefiniowane w klasie, a nie każda z nich w globalnej przestrzeni nazw. Po prostu używam mechaniki dostępnej w PHP5 do

  • nadaj im wszystkim przestrzeń nazw - unikając konfliktów nazw
  • utrzymuj je fizycznie razem, zamiast rozpraszać się po całym projekcie - inni programiści mogą łatwiej znaleźć to, co jest już dostępne i rzadziej wymyślają koło na nowo
  • pozwólcie, że użyję stałych klas zamiast globalnych definicji dla wszelkich magicznych wartości

jest to po prostu wygodny sposób na wymuszenie większej spójności i niższego sprzężenia.

I FWIW - nie ma czegoś takiego, przynajmniej w PHP5, jak „klasy statyczne”; metody i właściwości mogą być statyczne. Aby zapobiec tworzeniu instancji klasy, można również zadeklarować ją jako abstrakcyjną.

grantwparks
źródło
2
„Aby zapobiec tworzeniu instancji klasy, można też zadeklarować ją jako abstrakcyjną” - bardzo mi się to podoba. Wcześniej czytałem o ludziach, którzy używają funkcji __construct () jako funkcji prywatnej, ale myślę, że jeśli kiedykolwiek będę musiał, prawdopodobnie użyję abstrakcji
Kavi Siegel
7

Najpierw zadaj sobie pytanie, co będzie reprezentować ten obiekt? Instancja obiektu jest dobra do działania na oddzielnych zestawach danych dynamicznych.

Dobrym przykładem może być ORM lub warstwa abstrakcji bazy danych. Możesz mieć wiele połączeń z bazą danych.

$db1 = new Db(array('host' => $host1, 'username' => $username1, 'password' => $password1));
$db2 = new Db(array('host' => $host2, 'username' => $username2, 'password' => $password2));

Te dwa połączenia mogą teraz działać niezależnie:

$someRecordsFromDb1 = $db1->getRows($selectStatement);
$someRecordsFromDb2 = $db2->getRows($selectStatement);

Teraz w tym pakiecie / bibliotece mogą istnieć inne klasy, takie jak Db_Row itp., Które reprezentują określony wiersz zwracany przez instrukcję SELECT. Gdyby ta klasa Db_Row była klasą statyczną, to przy założeniu, że masz tylko jeden wiersz danych w jednej bazie danych i niemożliwe byłoby zrobienie tego, co mogłaby zrobić instancja obiektu. Dzięki instancji możesz teraz mieć nieograniczoną liczbę wierszy w nieograniczonej liczbie tabel w nieograniczonej liczbie baz danych. Jedynym ograniczeniem jest sprzęt serwera;).

Na przykład, jeśli metoda getRows w obiekcie Db zwraca tablicę obiektów Db_Row, możesz teraz operować na każdym wierszu niezależnie od siebie:

foreach ($someRecordsFromDb1 as $row) {
    // change some values
    $row->someFieldValue = 'I am the value for someFieldValue';
    $row->anotherDbField = 1;

    // now save that record/row
    $row->save();
}

foreach ($someRecordsFromDb2 as $row) {
    // delete a row
    $row->delete();
}

Dobrym przykładem klasy statycznej może być coś, co obsługuje zmienne rejestrowe lub zmienne sesji, ponieważ będzie tylko jeden rejestr lub jedna sesja na użytkownika.

W jednej części aplikacji:

Session::set('someVar', 'toThisValue');

A w innej części:

Session::get('someVar'); // returns 'toThisValue'

Ponieważ na sesję będzie przypadał tylko jeden użytkownik na raz, nie ma sensu tworzyć instancji dla sesji.

Mam nadzieję, że wraz z innymi odpowiedziami pomoże to wyjaśnić. Na marginesie, sprawdź „ spójność ” i „ sprzężenie ”. Przedstawiają one bardzo, bardzo dobre praktyki do wykorzystania podczas pisania kodu, które mają zastosowanie do wszystkich języków programowania.

Tres
źródło
6

Jeśli twoja klasa jest statyczna, oznacza to, że nie możesz przekazać jej obiektu do innych klas (ponieważ nie ma możliwości wystąpienia instancji), więc oznacza to, że wszystkie twoje klasy będą bezpośrednio używać tej klasy statycznej, co oznacza, że ​​twój kod jest teraz ściśle powiązany z klasą .

Ścisłe połączenie sprawia, że ​​kod jest mniej przydatny do ponownego wykorzystania, kruchy i podatny na błędy. Chcesz uniknąć klas statycznych, aby móc przekazywać instancję klasy do innych klas.

I tak, to tylko jeden z wielu innych powodów, z których niektóre zostały już wspomniane.

Muhammad Hasan Khan
źródło
3

Ogólnie rzecz biorąc, należy używać zmiennych składowych i funkcji składowych, chyba że jest to absolutnie konieczne dla wszystkich instancji lub jeśli tworzysz singleton. Korzystanie z danych składowych i funkcji składowych umożliwia ponowne wykorzystanie funkcji dla wielu różnych fragmentów danych, podczas gdy możesz mieć tylko jedną kopię danych, na których operujesz, jeśli używasz danych i funkcji statycznych. Dodatkowo, chociaż nie ma to zastosowania w PHP, statyczne funkcje i dane prowadzą do tego, że kod nie jest ponownie wprowadzany, podczas gdy dane klas ułatwiają ponowne wejście.

Michael Aaron Safyan
źródło
3

Chciałbym powiedzieć, że zdecydowanie istnieje przypadek, w którym chciałbym zmienne statyczne - w aplikacjach międzyjęzykowych. Możesz mieć klasę, do której przekazujesz język (np. $ _SESSION ['language']), a ona z kolei uzyskuje dostęp do innych klas zaprojektowanych w ten sposób:

Srings.php //The main class to access
StringsENUS.php  //English/US 
StringsESAR.php  //Spanish/Argentina
//...etc

Użycie Strings :: getString ("somestring") to dobry sposób na wyodrębnienie użycia języka z aplikacji. Możesz to zrobić w dowolny sposób, ale w tym przypadku posiadanie stałych z wartościami łańcuchowymi w każdym pliku łańcuchowym, do których ma dostęp klasa Strings, działa całkiem nieźle.

dudewad
źródło