Wann werden statische oder instanziierte Klassen verwendet?

170

PHP ist meine erste Programmiersprache. Ich kann mich nicht so recht darum kümmern, wann statische Klassen gegen instanziierte Objekte verwendet werden sollen.

Mir ist klar, dass Sie Objekte duplizieren und klonen können. In meiner gesamten Zeit mit PHP endete jedes Objekt oder jede Funktion jedoch immer als einzelner Rückgabewert (Array, String, Int) oder als ungültig.

Ich verstehe Konzepte in Büchern wie eine Videospiel-Charakterklasse. dupliziere Autoobjekt und mache das neue rot , das alles macht Sinn, aber was nicht ist, ist seine Anwendung in PHP- und Web-Apps.

Ein einfaches Beispiel. Ein Blog. Welche Objekte eines Blogs lassen sich am besten als statische oder instanziierte Objekte implementieren? Die DB-Klasse? Warum nicht einfach das Datenbankobjekt im globalen Bereich instanziieren? Warum nicht stattdessen jedes Objekt statisch machen? Was ist mit Leistung?

Ist alles nur Stil? Gibt es einen richtigen Weg, um dieses Zeug zu machen?

user73119
quelle

Antworten:

123

Dies ist eine ziemlich interessante Frage - und Antworten könnten auch interessant werden ^^

Der einfachste Weg, Dinge zu betrachten, könnte sein:

  • Verwenden Sie eine instanziierte Klasse, in der jedes Objekt eigene Daten hat (wie ein Benutzer einen Namen hat).
  • Verwenden Sie eine statische Klasse, wenn es sich nur um ein Tool handelt, das mit anderen Dingen funktioniert (z. B. einem Syntaxkonverter für BB-Code in HTML; es hat kein Eigenleben).

(Ja, ich gebe zu, wirklich sehr stark vereinfacht ...)

Eine Sache bei statischen Methoden / Klassen ist, dass sie Unit-Tests nicht erleichtern (zumindest in PHP, aber wahrscheinlich auch in anderen Sprachen).

Eine andere Sache über statische Daten ist, dass nur eine Instanz davon in Ihrem Programm vorhanden ist: Wenn Sie MyClass :: $ myData irgendwo auf einen Wert setzen, hat es diesen Wert und nur ihn, überall - Apropos Benutzer, Sie könnten nur einen Benutzer haben - was nicht so toll ist, oder?

Was könnte ich für ein Blog-System sagen? Es gibt nicht viel, was ich als statisch schreiben würde, denke ich; vielleicht die DB-Zugriffsklasse, aber wahrscheinlich nicht am Ende ^^

Pascal MARTIN
quelle
Verwenden Sie also grundsätzlich Objekte, wenn Sie mit einzigartigen Dingen wie einem Foto, einem Benutzer, einem Beitrag usw. arbeiten, und verwenden Sie statische Elemente, wenn sie für allgemeine Dinge gedacht sind?
Robert Rocha
1
Wenn Sie über den Benutzer sprechen, haben Sie nur einen aktiven Benutzer auf jeder Anfrage. Es wäre also sinnvoll, den aktiven Benutzer der Anfrage statisch zu haben
My1
69

Die beiden Hauptgründe gegen die Verwendung statischer Methoden sind:

  • Code mit statischen Methoden ist schwer zu testen
  • Code mit statischen Methoden ist schwer zu erweitern

Ein statischer Methodenaufruf in einer anderen Methode ist tatsächlich schlimmer als das Importieren einer globalen Variablen. In PHP sind Klassen globale Symbole. Jedes Mal, wenn Sie eine statische Methode aufrufen, verlassen Sie sich auf ein globales Symbol (den Klassennamen). Dies ist ein Fall, wenn global böse ist. Ich hatte Probleme mit dieser Art von Ansatz mit einer Komponente von Zend Framework. Es gibt Klassen, die statische Methodenaufrufe (Fabriken) verwenden, um Objekte zu erstellen. Es war mir unmöglich, dieser Instanz eine andere Fabrik zu liefern, um ein benutzerdefiniertes Objekt zurückzugeben. Die Lösung für dieses Problem besteht darin, zu Beginn des Programms nur Instanzen und Instace-Methoden zu verwenden und Singletons und dergleichen zu erzwingen.

Miško Hevery , der als Agile Coach bei Google arbeitet, hat eine interessante Theorie oder rät eher, die Objekterstellungszeit von der Zeit zu trennen, in der wir das Objekt verwenden. Der Lebenszyklus eines Programms ist also zweigeteilt. Der erste Teil ( main()sagen wir die Methode), der sich um die gesamte Objektverdrahtung in Ihrer Anwendung kümmert, und der Teil, der die eigentliche Arbeit erledigt.

Also anstatt zu haben:

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

Wir sollten lieber tun:

class HttpClient
{
    private $httpResponseFactory;

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

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

Und dann würden wir auf der Index- / Hauptseite Folgendes tun (dies ist der Schritt der Objektverdrahtung oder die Zeit, um das Diagramm der vom Programm zu verwendenden Instanzen zu erstellen):

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

Die Hauptidee besteht darin, die Abhängigkeiten von Ihren Klassen zu entkoppeln. Auf diese Weise ist der Code viel erweiterbarer und, was für mich am wichtigsten ist, testbar. Warum ist es wichtiger, testbar zu sein? Da ich nicht immer Bibliothekscode schreibe, ist die Erweiterbarkeit nicht so wichtig, aber die Testbarkeit ist wichtig, wenn ich Refactoring durchführe. Wie auch immer, testbarer Code liefert normalerweise erweiterbaren Code, so dass es sich nicht wirklich um eine Entweder-Oder-Situation handelt.

Miško Hevery unterscheidet auch klar zwischen Singletons und Singletons (mit oder ohne Großbuchstaben S). Der Unterschied ist sehr einfach. Singletons mit Kleinbuchstaben "s" werden durch die Verkabelung im Index / Main erzwungen. Sie instanziieren ein Objekt einer Klasse, die das Singleton-Muster nicht implementiert, und achten darauf, dass Sie diese Instanz nur an eine andere Instanz übergeben, die sie benötigt. Andererseits ist Singleton mit einem Großbuchstaben "S" eine Implementierung des klassischen (Anti) Musters. Grundsätzlich eine globale Verkleidung, die in der PHP-Welt wenig Verwendung findet. Ich habe bis jetzt noch keinen gesehen. Wenn Sie möchten, dass eine einzelne DB-Verbindung von allen Klassen verwendet wird, gehen Sie wie folgt vor:

$db = new DbConnection;

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

Wenn wir das oben genannte tun, ist es klar, dass wir einen Singleton haben und wir haben auch eine gute Möglichkeit, einen Mock oder einen Stub in unsere Tests zu injizieren. Es ist überraschend, wie Unit-Tests zu einem besseren Design führen. Aber es ist sehr sinnvoll, wenn Sie glauben, dass Tests Sie dazu zwingen, über die Art und Weise nachzudenken, wie Sie diesen Code verwenden würden.

/**
 * 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);
    }
}

Die einzige Situation, in der ich statische Elemente verwenden würde (und ich habe sie verwendet, um das JavaScript-Prototypobjekt in PHP 5.3 nachzuahmen), ist, wenn ich weiß, dass das jeweilige Feld instanzübergreifend denselben Wert hat. Zu diesem Zeitpunkt können Sie eine statische Eigenschaft und möglicherweise ein Paar statischer Getter / Setter-Methoden verwenden. Vergessen Sie jedoch nicht, die Möglichkeit hinzuzufügen, das statische Element mit einem Instanzmitglied zu überschreiben. Beispielsweise verwendete Zend Framework eine statische Eigenschaft, um den Namen der DB-Adapterklasse anzugeben, die in Instanzen von verwendet wird Zend_Db_Table. Es ist schon eine Weile her, seit ich sie benutzt habe, so dass es vielleicht nicht mehr relevant ist, aber so erinnere ich mich daran.

Statische Methoden, die sich nicht mit statischen Eigenschaften befassen, sollten Funktionen sein. PHP hat Funktionen und wir sollten sie verwenden.

Ionuț G. Stan
quelle
1
@Ionut, ich bezweifle das nicht ein bisschen. Mir ist klar, dass die Position / Rolle auch einen Wert hat. Wie alles, was mit XP / Agile zu tun hat, hat es jedoch einen wirklich schwimmenden Namen.
Jason
2
"Dependency Injection" ist meiner Meinung nach der zu kompliziert klingende Begriff dafür. Viel und viel darüber in SO und Google-Land zu lesen. Was "Singletons" angeht , hat mich Folgendes wirklich zum Nachdenken gebracht: slidehare.net/go_oh/… Ein Vortrag darüber, warum PHP-Singletons wirklich keinen Sinn ergeben. Hoffe das trägt ein wenig zu einer alten Frage bei :)
dudewad
22

In PHP kann statisch also auf Funktionen oder Variablen angewendet werden. Nicht statische Variablen sind an eine bestimmte Instanz einer Klasse gebunden. Nicht statische Methoden wirken auf eine Instanz einer Klasse. Bilden wir also eine Klasse namens BlogPost.

titlewäre ein nicht statisches Mitglied. Es enthält den Titel dieses Blogposts. Wir könnten auch eine Methode namens haben find_related(). Es ist nicht statisch, da es Informationen von einer bestimmten Instanz der Blogpost-Klasse erfordert.

Diese Klasse würde ungefähr so ​​aussehen:

class blog_post {
    public $title;
    public $my_dao;

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

Auf der anderen Seite können Sie mit statischen Funktionen eine Klasse wie die folgende schreiben:

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

In diesem Fall müssen Sie den Blog-Beitrag als Argument übergeben, da die Funktion statisch ist und nicht auf einen bestimmten Blog-Beitrag wirkt.

Grundsätzlich ist dies eine Frage des objektorientierten Designs. Ihre Klassen sind die Substantive in Ihrem System, und die Funktionen, die auf sie einwirken, sind die Verben. Statische Funktionen sind prozedural. Sie übergeben das Objekt der Funktionen als Argumente.


Update: Ich möchte auch hinzufügen, dass die Entscheidung selten zwischen Instanzmethoden und statischen Methoden und eher zwischen der Verwendung von Klassen und der Verwendung von assoziativen Arrays liegt. In einer Blogging-App lesen Sie beispielsweise entweder Blog-Beiträge aus der Datenbank und konvertieren sie in Objekte, oder Sie belassen sie in der Ergebnismenge und behandeln sie als assoziative Arrays. Anschließend schreiben Sie Funktionen, die assoziative Arrays oder Listen assoziativer Arrays als Argumente verwenden.

Im OO-Szenario schreiben Sie Methoden für Ihre BlogPostKlasse, die auf einzelne Posts wirken, und statische Methoden, die auf Sammlungen von Posts wirken.

Rafe
quelle
4
Diese Antwort ist ziemlich gut, besonders mit dem Update, das Sie durchgeführt haben, weil ich aus diesem Grund auf diese Frage gestoßen bin. Ich verstehe die Funktionalität von "::" vs "$ this", aber wenn Sie das Beispiel berücksichtigen, das Sie für Blogposts gegeben haben, die aus einer Datenbank in einem assoziativen Array extrahiert wurden, wird eine ganz neue Dimension hinzugefügt auch im zugrunde liegenden Ton dieser Frage.
Gerben Jacobs
Dies ist eine der besten praktischen (versus theoretischen) Antworten auf diese häufig gestellte PHP-Frage. Der Nachtrag ist sehr hilfreich. Ich denke, dies ist die Antwort, die viele PHP-Programmierer suchen, positiv bewertet!
Ajmedway
14

Ist alles nur Stil?

Ein langer Weg, ja. Sie können perfekt gute objektorientierte Programme schreiben, ohne jemals statische Elemente zu verwenden. In der Tat würden einige Leute argumentieren, dass statische Elemente in erster Linie eine Verunreinigung sind. Ich würde vorschlagen, dass Sie als Anfänger in oop versuchen, statische Mitglieder insgesamt zu vermeiden. Es wird Sie in die Richtung des Schreibens in einem objektorientierten und nicht prozeduralen Stil zwingen .

troelskn
quelle
13

Ich habe hier einen anderen Ansatz für die meisten Antworten, insbesondere bei Verwendung von PHP. Ich denke, alle Klassen sollten statisch sein, es sei denn, Sie haben einen guten Grund, warum nicht. Einige der "Warum nicht" Gründe sind:

  • Sie benötigen mehrere Instanzen der Klasse
  • Ihre Klasse muss erweitert werden
  • Teile Ihres Codes können die Klassenvariablen nicht mit anderen Teilen teilen

Lassen Sie mich ein Beispiel nehmen. Da jedes PHP-Skript HTML-Code erzeugt, verfügt mein Framework über eine HTML-Writer-Klasse. Dies stellt sicher, dass keine andere Klasse versucht, HTML zu schreiben, da es sich um eine spezielle Aufgabe handelt, die auf eine einzelne Klasse konzentriert werden sollte.

Normalerweise verwenden Sie die HTML-Klasse wie folgt:

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

Jedes Mal, wenn get_buffer () aufgerufen wird, wird alles zurückgesetzt, sodass die nächste Klasse, die den HTML-Writer verwendet, in einem bekannten Zustand startet.

Alle meine statischen Klassen haben eine init () - Funktion, die aufgerufen werden muss, bevor die Klasse zum ersten Mal verwendet wird. Dies ist eher eine Konvention als eine Notwendigkeit.

Die Alternative zu einer statischen Klasse ist in diesem Fall chaotisch. Sie möchten nicht, dass jede Klasse, die ein kleines Stück HTML schreiben muss, eine Instanz des HTML-Writers verwalten muss.

Jetzt werde ich Ihnen ein Beispiel geben, wann Sie keine statischen Klassen verwenden sollten. Meine Formularklasse verwaltet eine Liste von Formularelementen wie Texteingaben, Dropdown-Listen und mehr. Es wird normalerweise folgendermaßen verwendet:

$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

Es gibt keine Möglichkeit, dies mit statischen Klassen zu tun, insbesondere wenn man bedenkt, dass einige der Konstruktoren jeder hinzugefügten Klasse viel Arbeit leisten. Auch die Vererbungskette für alle Elemente ist recht komplex. Dies ist also ein klares Beispiel, bei dem statische Klassen nicht verwendet werden sollten.

Die meisten Dienstprogrammklassen, z. B. das Konvertieren / Formatieren von Zeichenfolgen, sind gute Kandidaten für eine statische Klasse. Meine Regel ist einfach: In PHP wird alles statisch, es sei denn, es gibt einen Grund, warum dies nicht der Fall sein sollte.

JG Estiot
quelle
Können Sie ein Beispiel für den folgenden Punkt schreiben? "Teile Ihres Codes können die Klassenvariablen nicht mit anderen Teilen teilen" #JG Estiot
khurshed alam
10

"Ein statischer Methodenaufruf in einer anderen Methode ist tatsächlich schlimmer als der Import einer globalen Variablen." (definieren Sie "schlechter") ... und "Statische Methoden, die sich nicht mit statischen Eigenschaften befassen, sollten Funktionen sein".

Dies sind beide ziemlich weitreichende Aussagen. Wenn ich eine Reihe von Funktionen habe, die sich auf das Thema beziehen, aber Instanzdaten völlig unangemessen sind, würde ich sie lieber in einer Klasse definieren lassen und nicht jede im globalen Namespace. Ich benutze nur die in PHP5 verfügbaren Mechaniken

  • Geben Sie allen einen Namespace, um Namenskonflikte zu vermeiden
  • Halten Sie sie physisch zusammen, anstatt sich über ein Projekt zu verteilen. Andere Entwickler können leichter feststellen, was bereits verfügbar ist, und es ist weniger wahrscheinlich, dass sie das Rad neu erfinden
  • Lassen Sie mich Klassenkonstanten anstelle von globalen Definitionen für magische Werte verwenden

Es ist nur eine bequeme Möglichkeit, eine höhere Kohäsion und eine niedrigere Kopplung zu erzwingen.

Und FWIW - zumindest in PHP5 gibt es keine "statischen Klassen"; Methoden und Eigenschaften können statisch sein. Um die Instanziierung der Klasse zu verhindern, kann man sie auch als abstrakt deklarieren.

Grantwparks
quelle
2
"Um die Instanziierung der Klasse zu verhindern, kann man sie auch als abstrakt deklarieren" - das gefällt mir sehr gut. Ich habe zuvor über Leute gelesen, die __construct () zu einer privaten Funktion machen, aber ich denke, wenn ich jemals muss, werde ich wahrscheinlich abstrakt verwenden
Kavi Siegel
7

Fragen Sie sich zuerst, was dieses Objekt darstellen wird. Eine Objektinstanz eignet sich für die Bearbeitung separater Sätze dynamischer Daten.

Ein gutes Beispiel wäre ORM oder Datenbankabstraktionsschicht. Möglicherweise haben Sie mehrere Datenbankverbindungen.

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

Diese beiden Verbindungen können jetzt unabhängig voneinander betrieben werden:

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

In diesem Paket / dieser Bibliothek befinden sich möglicherweise andere Klassen wie Db_Row usw., um eine bestimmte Zeile darzustellen, die von einer SELECT-Anweisung zurückgegeben wird. Wenn diese Db_Row-Klasse eine statische Klasse wäre, würde dies voraussetzen, dass Sie nur eine Datenzeile in einer Datenbank haben und es unmöglich wäre, das zu tun, was eine Objektinstanz könnte. Mit einer Instanz können Sie jetzt eine unbegrenzte Anzahl von Zeilen in einer unbegrenzten Anzahl von Tabellen in einer unbegrenzten Anzahl von Datenbanken haben. Die einzige Grenze ist die Serverhardware;).

Wenn die getRows-Methode für das Db-Objekt beispielsweise ein Array von Db_Row-Objekten zurückgibt, können Sie jetzt jede Zeile unabhängig voneinander bearbeiten:

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();
}

Ein gutes Beispiel für eine statische Klasse wäre etwas, das Registrierungsvariablen oder Sitzungsvariablen verarbeitet, da es nur eine Registrierung oder eine Sitzung pro Benutzer gibt.

In einem Teil Ihrer Bewerbung:

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

Und in einem anderen Teil:

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

Da es immer nur einen Benutzer pro Sitzung geben wird, macht es keinen Sinn, eine Instanz für die Sitzung zu erstellen.

Ich hoffe, dies hilft zusammen mit den anderen Antworten, um die Dinge zu klären. Überprüfen Sie als Randnotiz " Kohäsion " und " Kopplung ". Sie beschreiben einige sehr, sehr gute Praktiken beim Schreiben Ihres Codes, die für alle Programmiersprachen gelten.

Tres
quelle
6

Wenn Ihre Klasse statisch ist, bedeutet dies, dass Sie ihr Objekt nicht an andere Klassen weitergeben können (da keine Instanz möglich ist). Dies bedeutet, dass alle Ihre Klassen diese statische Klasse direkt verwenden, was bedeutet, dass Ihr Code jetzt eng mit der Klasse gekoppelt ist .

Durch die enge Kopplung ist Ihr Code weniger wiederverwendbar, zerbrechlich und fehleranfällig. Sie möchten statische Klassen vermeiden, um die Instanz der Klasse an andere Klassen übergeben zu können.

Und ja, dies ist nur einer von vielen anderen Gründen, von denen einige bereits erwähnt wurden.

Muhammad Hasan Khan
quelle
3

Im Allgemeinen sollten Sie Elementvariablen und Elementfunktionen verwenden, es sei denn, diese müssen unbedingt von allen Instanzen gemeinsam genutzt werden oder Sie erstellen einen Singleton. Durch die Verwendung von Elementdaten und Elementfunktionen können Sie Ihre Funktionen für mehrere verschiedene Datenelemente wiederverwenden, während Sie nur eine Kopie der Daten haben können, mit denen Sie arbeiten, wenn Sie statische Daten und Funktionen verwenden. Obwohl dies für PHP nicht zutreffend ist, führen statische Funktionen und Daten dazu, dass Code nicht wiedereintrittsfähig ist, während Klassendaten die Wiedereintrittsfähigkeit erleichtern.

Michael Aaron Safyan
quelle
3

Ich möchte sagen, dass es definitiv einen Fall gibt, in dem ich statische Variablen in sprachübergreifenden Anwendungen haben möchte. Sie könnten eine Klasse haben, an die Sie eine Sprache übergeben (z. B. $ _SESSION ['language']), und die wiederum auf andere Klassen zugreift, die so gestaltet sind:

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

Die Verwendung von Strings :: getString ("somestring") ist eine gute Möglichkeit, Ihren Sprachgebrauch aus Ihrer Anwendung zu abstrahieren. Sie können es tun, wie Sie möchten, aber in diesem Fall funktioniert es ziemlich gut, wenn jede String-Datei Konstanten mit String-Werten hat, auf die die Strings-Klasse zugreift.

Dudewad
quelle