Quando utilizzare le classi statiche vs istanze


170

PHP è il mio primo linguaggio di programmazione. Non riesco a capire bene quando usare le classi statiche rispetto agli oggetti istanziati.

Mi rendo conto che puoi duplicare e clonare oggetti. Comunque in tutto il mio tempo usando php qualsiasi oggetto o funzione finiva sempre come un singolo valore di ritorno (array, stringa, int) o vuoto.

Comprendo concetti nei libri come una classe di personaggi dei videogiochi. duplicare l'oggetto auto e rendere rosso quello nuovo , tutto ha un senso, ma ciò che non lo fa è la sua applicazione in php e applicazioni web.

Un semplice esempio Un blog. Quali oggetti di un blog sarebbero implementati al meglio come oggetti statici o istanziati? La classe DB? Perché non creare un'istanza dell'oggetto db nell'ambito globale? Perché non rendere statico ogni oggetto? Che dire delle prestazioni?

È tutto solo stile? C'è un modo corretto di fare queste cose?

Risposte:


123

Questa è una domanda piuttosto interessante - e anche le risposte potrebbero diventare interessanti ^^

Il modo più semplice di considerare le cose potrebbe essere:

  • usa una classe instanciata in cui ogni oggetto ha i dati per conto suo (come un utente ha un nome)
  • usa una classe statica quando è solo uno strumento che funziona su altre cose (come, ad esempio, un convertitore di sintassi per codice BB in HTML; non ha una vita propria)

(Sì, lo ammetto, davvero troppo semplificato ...)

Una cosa sui metodi / classi statici è che non facilitano i test unitari (almeno in PHP, ma probabilmente anche in altre lingue).

Un'altra cosa che riguarda i dati statici è che esiste solo un'istanza di questi nel tuo programma: se imposti MyClass :: $ myData su un valore da qualche parte, avrà questo valore e solo esso, ovunque - Parlando dell'utente, potresti avere un solo utente - il che non è eccezionale, vero?

Per un sistema di blog, cosa potrei dire? Non c'è molto che scriverei come statico, in realtà, penso; forse la classe di accesso al DB, ma probabilmente no, alla fine ^^


Quindi fondamentalmente usi gli oggetti quando lavori con cose uniche come una foto, un utente, un post, ecc. E usi statico quando è pensato per cose generali?
Robert Rocha,

1
per quanto riguarda l'utente, hai solo un utente attivo per ogni richiesta. quindi avere un utente attivo della richiesta statica avrebbe senso
My1

69

I due motivi principali per non utilizzare metodi statici sono:

  • è difficile testare il codice usando metodi statici
  • il codice che utilizza metodi statici è difficile da estendere

Avere una chiamata di metodo statico all'interno di un altro metodo è in realtà peggio che importare una variabile globale. In PHP, le classi sono simboli globali, quindi ogni volta che chiami un metodo statico fai affidamento su un simbolo globale (il nome della classe). Questo è un caso in cui il globale è malvagio. Ho avuto problemi con questo tipo di approccio con alcuni componenti di Zend Framework. Ci sono classi che usano chiamate a metodi statici (factory) per costruire oggetti. Per me era impossibile fornire un'altra fabbrica a quell'istanza per ottenere la restituzione di un oggetto personalizzato. La soluzione a questo problema è usare solo istanze e metodi di installazione e applicare singleton e simili all'inizio del programma.

Miško Hevery , che lavora come Agile Coach presso Google, ha una teoria interessante, o meglio consiglia, che dovremmo separare il tempo di creazione dell'oggetto dal tempo in cui lo utilizziamo. Quindi il ciclo di vita di un programma è diviso in due. La prima parte (il main()metodo diciamo), che si occupa di tutto il cablaggio dell'oggetto nell'applicazione e della parte che fa il lavoro effettivo.

Quindi invece di avere:

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

Dovremmo piuttosto fare:

class HttpClient
{
    private $httpResponseFactory;

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

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

E poi, nella pagina indice / principale, faremmo (questa è la fase di cablaggio dell'oggetto, o il tempo per creare il grafico delle istanze che verranno utilizzate dal programma):

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

L'idea principale è quella di separare le dipendenze dalle tue classi. In questo modo il codice è molto più estensibile e, la parte più importante per me, testabile. Perché è più importante essere testabili? Poiché non scrivo sempre il codice della libreria, quindi l'estensibilità non è così importante, ma la testabilità è importante quando eseguo il refactoring. Ad ogni modo, il codice testabile di solito produce codice estensibile, quindi non è davvero una situazione o.

Miško Hevery fa anche una chiara distinzione tra singoli e singoli (con o senza la S maiuscola). La differenza è molto semplice I singoli con una "s" minuscola sono applicati dal cablaggio nell'indice / principale. Si crea un'istanza di un oggetto di una classe che non implementa il modello Singleton e si cura di passare tale istanza a qualsiasi altra istanza che ne abbia bisogno. D'altra parte, Singleton, con la "S" maiuscola è un'implementazione del modello classico (anti). Fondamentalmente un globale sotto mentite spoglie che non ha molto uso nel mondo PHP. Non ne ho visto uno fino a questo punto. Se vuoi che una singola connessione DB sia utilizzata da tutte le tue classi, è meglio farlo in questo modo:

$db = new DbConnection;

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

Facendo quanto sopra è chiaro che abbiamo un singleton e abbiamo anche un bel modo di iniettare un finto o uno stub nei nostri test. È sorprendentemente come i test unitari portano a un design migliore. Ma ha molto senso pensare che i test ti costringano a pensare al modo in cui useresti quel codice.

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

L'unica situazione in cui userei (e li ho usati per imitare l'oggetto prototipo JavaScript in PHP 5.3) i membri statici è quando so che il rispettivo campo avrà lo stesso valore tra istanze. A quel punto puoi usare una proprietà statica e forse una coppia di metodi getter / setter statici. In ogni caso, non dimenticare di aggiungere la possibilità di sovrascrivere il membro statico con un membro di istanza. Ad esempio, Zend Framework utilizzava una proprietà statica per specificare il nome della classe dell'adattatore DB utilizzata nelle istanze di Zend_Db_Table. È da un po 'che non li uso, quindi potrebbe non essere più pertinente, ma è così che me lo ricordo.

I metodi statici che non trattano le proprietà statiche dovrebbero essere funzioni. PHP ha funzioni e dovremmo usarle.


1
@Iout, non ne dubito un po '. Mi rendo conto che c'è un valore anche nella posizione / ruolo; tuttavia, come tutto ciò che riguarda XP / Agile, ha un nome davvero floozy.
Jason,

2
"Iniezione delle dipendenze" Credo sia il termine suonante troppo complicato per questo. MOLTE e molte letture su tutto questo su SO e google-land. Per quanto riguarda i "singletons", ecco qualcosa che mi ha fatto davvero pensare: slideshare.net/go_oh/… Un discorso sul perché i singleton PHP non hanno davvero senso. Spero che questo contribuisca un po 'a una vecchia domanda :)
amico

22

Quindi in PHP statico può essere applicato a funzioni o variabili. Le variabili non statiche sono legate a un'istanza specifica di una classe. I metodi non statici agiscono su un'istanza di una classe. Quindi inventiamo una classe chiamata BlogPost.

titlesarebbe un membro non statico. Contiene il titolo di quel post sul blog. Potremmo anche avere un metodo chiamato find_related(). Non è statico perché richiede informazioni da un'istanza specifica della classe di post sul blog.

Questa classe sarebbe simile a questa:

class blog_post {
    public $title;
    public $my_dao;

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

D'altra parte, usando le funzioni statiche, potresti scrivere una classe come questa:

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

In questo caso, poiché la funzione è statica e non agisce su alcun post di blog specifico, è necessario passare il post di blog come argomento.

Fondamentalmente questa è una domanda sul design orientato agli oggetti. Le tue classi sono i sostantivi nel tuo sistema e le funzioni che agiscono su di essi sono i verbi. Le funzioni statiche sono procedurali. Si passa l'oggetto delle funzioni come argomenti.


Aggiornamento: aggiungerei anche che la decisione è raramente tra metodi di istanza e metodi statici e altro tra l'uso di classi e l'uso di array associativi. Ad esempio, in un'app di blog, leggi i post del blog dal database e li converti in oggetti oppure li lasci nel set di risultati e li tratti come array associativi. Quindi si scrivono funzioni che accettano come array argomenti o elenchi di array associativi.

Nello scenario OO, scrivi metodi sulla tua BlogPostclasse che agiscono su singoli post e scrivi metodi statici che agiscono su raccolte di post.


4
Questa risposta è abbastanza buona, specialmente con l'aggiornamento che hai fatto, perché questa è la ragione per cui ho finito con questa domanda. Comprendo la funzionalità di "::" vs "$ this", ma quando aggiungi l'account, l'esempio che hai fatto dei blogposts viene estratto da un DB in un array associativo. Aggiunge una dimensione completamente nuova, ovvero anche nel tono sottostante di questa domanda.
Gerben Jacobs,

Questa è una delle migliori risposte pratiche (contro teoriche) a questa domanda PHP molto richiesta, l'addendum è molto utile. Penso che questa sia la risposta che molti programmatori PHP stanno cercando, votata!
ajmedway,

14

È tutto solo stile?

Una lunga strada, sì. Puoi scrivere programmi perfettamente orientati agli oggetti senza mai usare membri statici. In effetti alcune persone sostengono che i membri statici sono in primo luogo un'impurità. Vorrei suggerire che - come principiante in oop - cerchi di evitare tutti i membri statici. Ti costringerà nella direzione della scrittura in uno stile orientato agli oggetti piuttosto che in uno stile procedurale .


13

Ho un approccio diverso alla maggior parte delle risposte qui, specialmente quando utilizzo PHP. Penso che tutte le classi dovrebbero essere statiche a meno che tu non abbia una buona ragione per non farlo. Alcuni dei motivi "why not" sono:

  • Sono necessarie più istanze della classe
  • La tua classe deve essere estesa
  • Parti del codice non possono condividere le variabili di classe con altre parti

Lasciami fare un esempio. Poiché ogni script PHP produce codice HTML, il mio framework ha una classe di scrittura html. Questo assicura che nessun'altra classe tenterà di scrivere HTML in quanto è un'attività specializzata che dovrebbe essere concentrata in una singola classe.

Tipicamente, useresti la classe html in questo modo:

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

Ogni volta che viene chiamato get_buffer (), reimposta tutto in modo che la classe successiva per utilizzare il writer html inizi in uno stato noto.

Tutte le mie classi statiche hanno una funzione init () che deve essere chiamata prima che la classe venga usata per la prima volta. Questo è più per convenzione che per necessità.

L'alternativa a una classe statica in questo caso è disordinata. Non vorrai che ogni classe che ha bisogno di scrivere un po 'di html debba gestire un'istanza del writer html.

Ora ti darò un esempio di quando non usare le classi statiche. La mia classe di moduli gestisce un elenco di elementi del modulo come input di testo, elenchi a discesa e altro. In genere viene utilizzato in questo modo:

$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

Non è possibile farlo con le classi statiche, soprattutto considerando che alcuni costruttori di ogni classe aggiunta svolgono molto lavoro. Inoltre, la catena di eredità per tutti gli elementi è piuttosto complessa. Quindi questo è un chiaro esempio in cui le classi statiche non dovrebbero essere usate.

La maggior parte delle classi di utilità come quelle che convertono / formattano le stringhe sono buoni candidati per essere una classe statica. La mia regola è semplice: tutto diventa statico in PHP a meno che non ci sia una ragione per cui non dovrebbe.


puoi scrivere un esempio del punto seguente "Parti del tuo codice non possono condividere le variabili di classe con altre parti" #JG Estiot
khurshed alam

10

"Avere una chiamata di metodo statico all'interno di un altro metodo è in realtà peggio che importare una variabile globale." (definisci "peggio") ... e "I metodi statici che non trattano le proprietà statiche dovrebbero essere funzioni".

Queste sono entrambe affermazioni piuttosto ampie. Se ho una serie di funzioni correlate all'argomento, ma i dati di istanza sono totalmente inappropriati, preferirei che fossero definiti in una classe e non ciascuno di essi nello spazio dei nomi globale. Sto solo usando la meccanica disponibile in PHP5 a

  • dare a tutti uno spazio dei nomi, evitando qualsiasi scontro tra nomi
  • tienili fisicamente posizionati insieme invece di disperdersi in un progetto: altri sviluppatori possono trovare più facilmente ciò che è già disponibile e hanno meno probabilità di reinventare la ruota
  • fammi usare i cons di classe anziché le definizioni globali per qualsiasi valore magico

è solo un modo conveniente per applicare una maggiore coesione e un accoppiamento più basso.

E FWIW - non esiste nulla di simile, almeno in PHP5, come "classi statiche"; i metodi e le proprietà possono essere statici. Per evitare l'istanza della classe, è possibile dichiararla astratta.


2
"Per evitare l'istanza della classe, si può anche dichiararla astratta" - Mi piace molto. In precedenza avevo letto di persone che rendevano __construct () una funzione privata, ma penso che se mai dovessi averne bisogno, probabilmente userò l'abstract
Kavi Siegel,

7

Prima chiediti, cosa rappresenterà questo oggetto? Un'istanza di oggetto è utile per operare su set separati di dati dinamici.

Un buon esempio potrebbe essere ORM o livello di astrazione del database. Potresti avere più connessioni al database.

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

Queste due connessioni ora possono funzionare in modo indipendente:

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

Ora all'interno di questo pacchetto / libreria, potrebbero esserci altre classi come Db_Row, ecc. Per rappresentare una riga specifica restituita da un'istruzione SELECT. Se questa classe Db_Row fosse una classe statica, ciò presupporrebbe che tu abbia una sola riga di dati in un database e che sarebbe impossibile fare ciò che un'istanza di oggetto potrebbe fare. Con un'istanza, ora puoi avere un numero illimitato di righe in un numero illimitato di tabelle in un numero illimitato di database. L'unico limite è l'hardware del server;).

Ad esempio, se il metodo getRows sull'oggetto Db restituisce una matrice di oggetti Db_Row, ora è possibile operare su ciascuna riga indipendentemente l'una dall'altra:

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

Un buon esempio di una classe statica potrebbe essere qualcosa che gestisce le variabili di registro o variabili di sessione poiché ci sarà un solo registro o una sessione per utente.

In una parte dell'applicazione:

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

E in un'altra parte:

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

Dal momento che ci sarà sempre un solo utente alla volta per sessione, non ha senso creare un'istanza per la sessione.

Spero che questo aiuti, insieme alle altre risposte per chiarire le cose. Come nota a margine, controlla " coesione " e " accoppiamento ". Descrivono alcune, ottime pratiche da usare quando si scrive il codice che si applicano a tutti i linguaggi di programmazione.


6

Se la tua classe è statica significa che non puoi passare il suo oggetto ad altre classi (poiché non è possibile alcuna istanza), quindi ciò significa che tutte le tue classi useranno direttamente quella classe statica, il che significa che il tuo codice è ora strettamente associato alla classe .

L'accoppiamento stretto rende il codice meno riutilizzabile, fragile e soggetto a bug. Si desidera evitare che le classi statiche possano passare l'istanza della classe ad altre classi.

E sì, questo è solo uno dei tanti altri motivi alcuni dei quali sono già stati menzionati.


3

In generale, è necessario utilizzare le variabili membro e le funzioni membro a meno che non sia assolutamente necessario condividerle tra tutte le istanze o a meno che non si stia creando un singleton. L'uso dei dati e delle funzioni membro consente di riutilizzare le funzioni per più dati diversi, mentre è possibile disporre di una sola copia di dati su cui si opera se si utilizzano dati e funzioni statici. Inoltre, sebbene non applicabile a PHP, le funzioni statiche e i dati portano a non rientrare nel codice, mentre i dati di classe facilitano il rientro.


3

Vorrei dire che esiste sicuramente un caso in cui mi piacerebbe che le variabili statiche si trovassero nelle applicazioni multilingue. Potresti avere una classe in cui passi una lingua (ad esempio $ _SESSION ['lingua']) e a sua volta accede ad altre classi progettate in questo modo:

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

L'uso di Strings :: getString ("somestring") è un bel modo per sottrarre l'utilizzo della lingua dall'applicazione. Puoi farlo come ti pare, ma in questo caso ogni file di stringhe ha costanti con valori di stringa a cui accede la classe Strings funziona abbastanza bene.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.