Ruolo e importanza del metodo statico in OOP

6

Sfondo

Pensando a OOP sento che lega insieme dati e comportamenti, prendendo l'esempio del mondo reale abbiamo già un tipo di dati array che è una collezione di tipo omogeneo e in Java abbiamo creato una bella astrazione su di esso, quindi, abbiamo metodi come clone che fa perfettamente il suo lavoro, cioè abbiamo dati e abbiamo associato un'operazione per rendere il client code semplice e logica di clone incapsulato dietro una bella API .

Spero di essere corretto fino ad ora.

Domanda

Quindi, supponiamo di dover ordinare un array, sarebbe bene avere il metodo sort esposto come l'API che fa l'ordinamento sugli elementi dell'array il cui ordinamento è basato sul tipo di elemento che sembra perfetto ma aspetto che non vedo tali metodi su Array ADT e qui arriva la confusione che abbiamo pagina piena di documentazione qui che elenca un numero di metodi statici . Se ricordo bene, è un modello molto odiato nella comunità TDD di avere metodi statici perché rende un test un inferno, anche noi ignoriamo la loro preoccupazione, quindi anche io vedo una violazione del concetto OOP qui, abbiamo già dati quindi perché non hanno tutti questi metodi nella matrice stessa?

Aggiorna

La presenza dell'esempio di Array qui non significa che io sia confuso riguardo l'Array in Java, la mia preoccupazione principale riguarda il metodo statico in generale. Normalmente mi trovo nella situazione in cui penso che sarebbe meglio avere un metodo statico, ma molte volte ho visto la comunità andare contro di esso quindi ci dovrebbe essere un solido ragionamento sul fatto che il mio design sia imperfetto o che il metodo statico sia la migliore alternativa in quella situazione.

    
posta CodeYogi 19.06.2016 - 06:29
fonte

4 risposte

3

Questo è un design specifico di Java. Ad esempio in D , sort è un metodo di istanza sugli array, non un metodo statico come in Java.

Ma gli array Java sono speciali. Sono definiti come oggetti, ma non sono istanze di classi. Gli unici metodi di istanza che hanno sono ereditati direttamente da Object . Non esistono metodi di istanza specifici per Array poiché non esiste una classe di Array in cui potrebbero essere definiti. (Tieni presente che la classe Arrays a cui fai il link è solo una classe di utilità - gli array non sono esempi di questa classe.)

Un design più logico sarebbe che gli array fossero istanze della classe Array<T> , ma poiché la versione iniziale di Java non aveva generici, questo design non era possibile.

Negli elenchi generici (come List<E> ) il metodo sort è un metodo di istanza. Quindi possiamo concludere che i progettisti di Java concordano che sort dovrebbe idealmente essere un metodo di istanza, ma non era possibile per gli array a causa delle limitazioni nella progettazione del linguaggio di base.

In C # è stato scelto un design diverso e gli array sono in realtà istanze di una classe Array , che consente metodi di istanza specifici dell'array (Ordinamento è comunque definito come statico, per qualsiasi motivo).

In breve: gli array in Java non sono OO "veri", quindi non possono confermare i principi di progettazione OO.

    
risposta data 19.06.2016 - 09:28
fonte
14

L'idea che i metodi statici siano impossibili da testare unitariamente è un mito che si è dimostrato difficile da uccidere.

Ciò che rende difficile testare un metodo in modo isolato è roba come dipendenze nascoste e accesso allo stato statico. Non c'è alcuna differenza tra un metodo statico e un metodo di istanza, ad eccezione del fatto che le invocazioni dei metodi di istanza ottengono un parametro di riferimento this nascosto utilizzato per accedere ai membri privati.

Se il tuo metodo ha un'API onesta, cioè indica chiaramente quali dipendenze ha, e se è puro in ogni altro senso, allora è banalmente controllabile indipendentemente dal fatto che sia statico.

Se il tuo metodo aggiorna le variabili globali, new s up le connessioni al database e le scritture su disco, allora non è testabile indipendentemente dal fatto che si tratti o meno di un metodo di istanza.

Sì, i metodi di istanza (virtuali) possono essere sovrascritti, ma questo è abbastanza vicino al punto. Accesso allo stato globale e scrittura su disco E contrassegnare il metodo virtuale non lo rende testabile. Ciò lo rende semplicemente stoppabile, quindi puoi disaccoppiarlo durante il test di ALTRE cose. E questo non significa nemmeno che un'enorme quantità di falsi, finte e spie, ecc., Sia un test anti-modello nella sua direzione e porti a un codice di test rigido e fragile.

La chiave è, ed è sempre stato quello di creare moduli e classi coesivi e disaccoppiati. Finché sei onesto nella tua API e non ti tocchi e non tiri fuori le cose dal nulla, allora non ci sono problemi.

Ora, per quanto riguarda il motivo per cui si dovrebbe fare un metodo come sort un metodo di supporto statico invece di un metodo di istanza, potrebbe avere diverse ragioni. Alla fine della giornata è solo una decisione di progettazione e potresti andare in entrambi i modi. Una cosa che non aiuta a mettere troppi (o addirittura nessuno) metodi di utilità su entità è che la loro api può facilmente diventare gonfia. Anche la classe crescerà e crescerà. Tutti hanno bisogno di diversi aiutanti, quindi come decidete quali sono "importanti" abbastanza da mettere il tipo e quali da inserire in una classe esterna? E come programmatore, come fai a sapere dove cercare?

Esiste un buon caso per mantenere separate le classi che trasportano dati dalle classi di calcolo / elaborazione (ovviamente senza interrompere l'incapsulamento). Questo può consentire di creare un algoritmo di ordinamento generale per qualsiasi tipo implementando List<? extends Comparable> invece di doverne scrivere uno per gli array, uno per gli arrayylists, uno per gli elenchi collegati e così via (senza dover ricorrere all'utilizzo di una classe base astratta comune) .

    
risposta data 19.06.2016 - 11:38
fonte
5

Per semplificare:

  • Lo stato statico è negativo perché è effettivamente uno stato globale poiché tutti gli utenti hanno accesso ad esso. Il wrapping in un singleton non cambia nulla . In generale è bene evitare lo stato globale che è difficile da testare.
  • Funzioni statiche che sono pure funzioni o che modificano solo i loro argomenti stanno perfettamente bene. Se non fanno alcuna contabilità o mantengono alcun stato, non c'è niente di sbagliato in loro.

Per quanto riguarda gli array, sono semplicemente bizzarri in Java, come menzionano le altre risposte. Detto questo, anche altre raccolte vengono ordinate tramite Collections.sort piuttosto che un metodo di ordinamento su di esse. La logica è che una raccolta non dovrebbe essere consapevole di come dovrebbe essere ordinata da sola. Questo è più discutibile in quanto diversi linguaggi espongono questo direttamente alla raccolta (ad esempio, someArray.sort() di JavaScript, someArray.sort() di Swift o% co_de di% C e .Sort ).

Questa decisione spetta al progettista e entrambi gli approcci vanno bene e dipendono dal tuo modello mentale. È più chiaro quando discutiamo di aggiungere qualcosa a una raccolta o di rimuoverne qualcosa - credo che in questo caso la raccolta di base dovrebbe essere in grado di imporre invarianti. L'ordinamento è un'altra storia poiché qualsiasi cosa tu possa iterare puoi ordinare quale è uno strato che puoi usare come astrazione. (Anche se il dispacciamento non così efficiente e polimorfo ha ancora senso, secondo me).

    
risposta data 19.06.2016 - 11:56
fonte
0

Sono con te sulla natura non OOP della parte delle classi statiche. Ci sono diversi inconvenienti.

È una dipendenza nascosta dalla natura stessa dell' staticità. Non puoi semplicemente iniettare una classe, puoi iniettare solo un oggetto. L'esempio più vivido è, come @kai menzionato nei commenti, classi di utilità.

Le classi tendono ad essere grandi o enormi. Poiché le classi con metodi statici non hanno nulla a che fare con gli oggetti, non sanno chi sono, cosa dovrebbero fare e cosa non dovrebbero fare. I confini sono sfocati, quindi scriviamo solo una istruzione dopo l'altra. È difficile smettere finché non abbiamo finito con il nostro compito. È un processo inevitabile e non-OOP.

Quindi la classe sta diventando sempre meno coesa. Se la classe ha molte dipendenze, è probabile che faccia più del dovuto.

I metodi statici indicano che possono essere chiamati da qualsiasi luogo. Possono essere chiamati da molti contesti. Hanno molti clienti. Quindi se una classe ha bisogno di qualche piccolo comportamento speciale da implementare nel metodo statico, è necessario assicurarsi che nessuno degli altri client si sia rotto. Quindi un tale riutilizzo semplicemente non funziona. Posso confrontarlo con il nobile (e fallito) tentativo di comporre e riutilizzare i microservizi. Le classi risultanti sono troppo generiche e completamente non mantenibili. Ciò comporta che l'intero sistema sia strettamente accoppiato.

Per illustrare il mio punto, I'l; prendi il tuo esempio di classificazione Voglio che il mio codice sia dichiarativo, quindi separo la loro creazione e il lavoro. Quindi, invece di avere un metodo sort , avrei una classe Sorted che implementa un'interfaccia Array (non parliamo di alcun linguaggio specifico ora). Quindi il mio codice potrebbe essere simile a:

$array =
    new Sorted(
        new Filtered(
            new Mapped(
                [1, 2, 3],
                function (int $i) {
                    return $i * 3;
                }
            ),
            function (int $i) {
                return $i %2 == 0;
            }
        ),
        function ($a, $b) {
            return $b > $a;
        }
    )
;

print_r($array->value()); // here the objects actually work
    
risposta data 24.11.2017 - 08:53
fonte