Quali sono gli usi validi delle classi statiche?

8

Ho notato che quasi ogni volta che vedo i programmatori che usano classi statiche in linguaggi orientati agli oggetti come C #, lo fanno male. I problemi principali sono ovviamente lo stato globale e la difficoltà di scambiare implementazioni in fase di esecuzione o con mock / stub durante i test.

Per curiosità, ho esaminato alcuni dei miei progetti selezionando quelli che sono ben testati e quando ho fatto uno sforzo per pensare effettivamente all'architettura e al design. I due usi delle classi statiche che ho trovato sono:

  • La classe di utilità-qualcosa Preferirei evitare se stavo scrivendo questo codice oggi,

  • La cache dell'applicazione: l'uso della classe statica è chiaramente sbagliato. Cosa succede se voglio sostituirlo con MemoryCache o Redis?

Guardando .NET Framework, non vedo alcun esempio di un uso valido delle classi statiche. Ad esempio:

    La classe statica
  • File rende davvero doloroso passare a alternative. Ad esempio, cosa succede se è necessario passare a Storage isolato o memorizza i file direttamente nella memoria o ha bisogno di un provider che supporti le transazioni NTFS o almeno sia in grado di gestire i percorsi più lunghi di 259 caratteri ? Avere un'interfaccia comune e più implementazioni appare facile come usare File direttamente, con vantaggio di non dover riscrivere la maggior parte della base di codice quando i requisiti cambiano.

  • La classe statica
  • Console rende i test troppo complicati. Come dovrei garantire all'interno di una unit test che un metodo restituisca dati corretti da console? Dovrei modificare il metodo per inviare l'output ad un% co_de scrivibile che viene iniettato in fase di esecuzione? Sembra che una classe di console non statica sarebbe semplice come una statica, solo senza tutti gli svantaggi di una classe statica, ancora una volta.

  • Stream static non è un buon candidato; cosa succede se ho bisogno di passare ad aritmetica di precisione arbitraria? O per un'implementazione più recente e più veloce? O a uno stub che dirà, durante un test unitario, che un determinato metodo di una classe Math è stato effettivamente chiamato durante un test?

Su Programmer.SE, ho letto:

A parte i metodi di estensione, quali sono gli usi validi delle classi statiche, vale a dire i casi in cui Iniezione delle dipendenze o singleton sono impossibili o risulteranno in un design di qualità inferiore e in una maggiore estensibilità e manutenzione?

    
posta Arseni Mourzenko 19.12.2014 - 20:18
fonte

4 risposte

20

what if; what if; what if?

YAGNI

Scherzi a parte. Se qualcuno vuole utilizzare diverse implementazioni di File o Math o Console , allora può passare attraverso il dolore di astrarre quella via. Hai visto / usato Java?!? Le librerie standard Java sono un ottimo esempio di ciò che accade quando si astraggono le cose per motivi di astrazione. davvero devo passare attraverso 3 passaggi per creare il mio oggetto calendario ?

Tutto ciò che ho detto, non mi siederò qui e difendere strongmente le classi statiche. Lo stato statico è solidamente malvagio (anche se lo uso ancora occasionalmente per raggruppare / memorizzare le cose). Le funzioni statiche possono essere malvagie a causa di problemi di concorrenza e testabilità.

Ma le funzioni statiche pure non sono così terribili. Ci sono elementi elementari che non hanno senso astrarre perché non c'è un'alternativa sana per l'operazione. Ci sono implementazioni di una strategia che può essere statica perché sono già astratte tramite il delegato / funtore. E a volte a queste funzioni non è associata una classe di istanza, quindi le classi statiche vanno bene per organizzarle.

Inoltre, i metodi di estensione sono un uso pratico , anche se non sono classi filosoficamente statiche.

    
risposta data 19.12.2014 - 21:03
fonte
18

In una parola Semplicità.

Quando si disaccoppia troppo, si ottiene il ciao mondo dall'inferno.

void main(String[] args) {
    TextOutputFactory outputFactory = new TextOutputFactory();
    OutputStream stream = outputFactory.CreateStdOutputStream();

    Encoding encoding = new EncodingFactory.CreateUtf8Encoding();
    stream.Encoding = encoding;

    SystemConstant constant = new SystemConstant();
    stream.LineEnding = constant.PlatformLineEnding;

    stream.FlushOnEachLine = true;

    String greeting = new FixedSizeInMemoryString(); // We may have to switch to a file based string later!"
    greeting.SetContent("Hello world");
    greeting.Initialize();

    stream.SendContent(greeting);
    stream.EndCurrentLine();

    ProcessCompletion completion = new ProcessCompletion();
    completion.Status = constant.SuccessStatus;
    completion.ExitProgram();
}

Ma hey, almeno non c'è una configurazione XML, che è un bel tocco.

Le classi statiche consentono di ottimizzare per il caso rapido e comune. La maggior parte dei punti che hai menzionato possono essere risolti molto facilmente introducendo la tua astrazione su di loro, o usando cose come Console.Out.

    
risposta data 19.12.2014 - 23:14
fonte
10

Il caso dei metodi statici: Questo:

var c = Math.Max(a, Int32.Parse(b));

è molto più semplice e chiaro e più leggibile di questo:

IMathLibrary math = new DefaultMathLibrary();
IIntegerParser integerParser = new DefaultIntegerParser();
var c = math.Max(a, integerParser.Parse(b));

Ora aggiungi l'iniezione di dipendenza per nascondere le istanze esplicite, e vedi l'one-liner crescere in decine o centinaia di linee - tutto per supportare lo scenario improbabile che inventerai il tuo Max -implementazione che è significativamente più veloce di quello nel framework e devi essere in grado di passare in modo trasparente tra le alternative Max -implementazioni.

Il design delle API è un compromesso tra semplicità e flessibilità. Puoi sempre introdurre ulteriori livelli di riferimento indiretto ( ad infinitum ), ma devi valutare la convenienza del caso comune a vantaggio dei livelli aggiuntivi.

Ad esempio, è sempre possibile scrivere il proprio wrapper di istanza attorno all'API del file system se è necessario essere in grado di passare in modo trasparente tra i provider di archiviazione. Ma senza i metodi statici più semplici, tutti sarebbero costretti a creare un'istanza ogni volta che dovevano fare una cosa semplice come salvare un file. Sarebbe davvero un cattivo compromesso progettuale ottimizzare per un caso limite estremamente raro e rendere tutto più complicato per il caso comune. Lo stesso vale per Console : puoi creare facilmente il tuo wrapper se devi astrarre o prendere in giro la console, ma molto spesso usi la console per soluzioni rapide o per il debug, quindi vuoi semplicemente il modo più semplice per produrre del testo.

Inoltre, mentre "un'interfaccia comune con più implementazioni" è interessante, non c'è necessariamente un'astrazione "giusta" che unisca tutti i fornitori di storage che desideri. Potresti voler passare da un file all'altro all'archiviazione isolata, ma qualcun altro potrebbe voler passare da file, database e cookie per l'archiviazione. L'interfaccia comune sembrerebbe diversa, ed è impossibile prevedere tutte le possibili astrazioni che gli utenti potrebbero desiderare. È molto più flessibile fornire metodi statici come "primitive" e quindi consentire agli utenti di creare le proprie astrazioni e wrapper.

Il tuo esempio di Math è ancora più dubbio. Se si desidera passare a matematica di precisione arbitraria, è presumibile che siano necessari nuovi tipi numerici. Questo sarebbe un cambiamento fondamentale di tutto il tuo programma, e dover cambiare Math.Max() in ArbitraryPrecisionMath.Max() è certamente l'ultima delle tue preoccupazioni.

Detto questo, sono d'accordo che le classi statiche stateful sono nella maggior parte dei casi cattive.

    
risposta data 03.09.2015 - 17:56
fonte
0

In generale gli unici posti in cui userò le classi statiche sono quelli in cui la classe ospita funzioni pure che non attraversano i confini del sistema. Mi rendo conto che quest'ultimo è ridondante in quanto qualsiasi cosa che attraversa un confine è probabile per intento non puro. Vengo a questa conclusione sia da una sfiducia dello stato globale che da una prospettiva di facile analisi. Anche dove c'è una ragionevole aspettativa che ci sarà una singola implementazione per una classe che attraversa un confine di sistema (rete, file system, dispositivo I / O), scelgo di usare istanze piuttosto che classi statiche perché la possibilità di fornire una simulazione / L'implementazione falsa nei test unitari è fondamentale nello sviluppo di test rapidi delle unità senza alcun accoppiamento con i dispositivi reali. Nei casi in cui il metodo è una funzione pura senza alcun accoppiamento con un sistema / dispositivo esterno, ritengo che una classe statica possa essere appropriata, ma solo se non ostacola la testabilità. Trovo che i casi d'uso siano relativamente rari al di fuori delle classi quadro esistenti. Inoltre, ci possono essere casi in cui non hai scelta, ad esempio i metodi di estensione in C #.

    
risposta data 04.09.2015 - 16:50
fonte

Leggi altre domande sui tag