Quantità significativa del tempo, non riesco a pensare a un motivo per avere un oggetto invece di una classe statica. Gli oggetti hanno più vantaggi di quanto penso? [chiuso]

9

Capisco il concetto di un oggetto e, in quanto programmatore Java, sento che il paradigma OO mi viene piuttosto naturale nella pratica.

Comunque recentemente mi sono trovato a pensare:

Aspetta un secondo, quali sono in realtà i benefici pratici dell'uso di un oggetto rispetto all'uso di una classe statica (con incapsulamento e pratiche OO appropriate)?

Potrei pensare a due vantaggi dell'uso di un oggetto (entrambi sono significativi e potenti):

  1. Polymorphism: consente di scambiare la funzionalità in modo dinamico e flessibilmente durante il runtime. Permette anche di aggiungere nuove funzionalità 'parti' e alternative al sistema facilmente. Ad esempio se c'è una classe Car progettata per funzionare con oggetti Engine e tu vuoi aggiungere un nuovo Motore al sistema che l'auto può usare, puoi crearne uno nuovo Engine sottoclasse e semplicemente passare un oggetto di questa classe nel file Car oggetto, senza dover modificare nulla su Car . E puoi decidere di farlo durante il runtime.

  2. Essere in grado di "passare la funzionalità in giro": puoi passare un oggetto attorno al sistema in modo dinamico.

Ma ci sono ulteriori vantaggi per gli oggetti rispetto alle classi statiche?

Spesso quando aggiungo nuove 'parti' a un sistema, lo faccio creando una nuova classe e creando un'istanza di oggetti da essa.

Ma recentemente, quando mi sono fermato e ci ho pensato, mi sono reso conto che una classe statica avrebbe fatto lo stesso di un oggetto, in molti dei luoghi in cui normalmente utilizzo un oggetto.

Ad esempio, sto lavorando per aggiungere un meccanismo di salvataggio / caricamento file alla mia app.

Con un oggetto, la linea chiamante di codice sarà simile a questa: Thing thing = fileLoader.load(file);

Con una classe statica, sarebbe simile a questa: Thing thing = FileLoader.load(file);

Qual è la differenza?

Molto spesso non riesco a pensare a una ragione per istanziare un oggetto quando una classe statica semplice e normale agirebbe allo stesso modo. Ma nei sistemi OO, le classi statiche sono piuttosto rare. Quindi mi manca qualcosa.

Ci sono ulteriori vantaggi per gli oggetti diversi dai due che ho elencato? Per favore, spiega.

EDIT: per chiarire. Trovo gli oggetti molto utili quando si scambiano funzionalità o si passano dati in giro. Ad esempio, ho scritto un'app che compone le melodie. MelodyGenerator ha avuto diverse sottoclassi che creano melodie in modo diverso e oggetti di queste classi erano intercambiabili (modello di strategia).

Anche le melodie erano oggetti, dal momento che è utile passarli in giro. Così erano gli accordi e le scale.

Ma per quanto riguarda le parti "statiche" del sistema - che non verranno trasmesse? Ad esempio, un meccanismo "salva file". Perché dovrei implementarlo in un oggetto e non in una classe statica?

    
posta Aviv Cohn 03.06.2014 - 23:17
fonte

6 risposte

14

lol suona come una squadra su cui lavoravo;)

Java (e probabilmente C #) certamente supporta quello stile di programmazione. E io lavoro con persone che sono il primo istinto, "Posso renderlo un metodo statico!" Ma ci sono dei piccoli costi che ti raggiungono nel tempo.

1) Java è un linguaggio orientato agli oggetti. E fa impazzire i ragazzi funzionali, ma in realtà regge abbastanza bene. L'idea alla base di OO è quella di raggruppare le funzionalità con lo stato in modo da disporre di piccole unità di dati e funzionalità che conservano la loro semantica nascondendo lo stato ed esponendo solo le funzioni che hanno senso in quel contesto.

Passando a una classe con solo metodi statici, si interrompe la parte "stato" dell'equazione. Ma lo stato deve ancora vivere da qualche parte. Quindi quello che ho visto nel corso del tempo è che una classe con tutti i metodi statici inizia ad avere liste di parametri sempre più complicate, perché lo stato si sta spostando dalla classe e nelle chiamate alla funzione.

Dopo aver creato una classe con tutti i metodi statici, esegui e analizza solo quanti di questi metodi hanno un unico parametro comune. È un suggerimento che quel parametro dovrebbe essere la classe contenente per quelle funzioni, altrimenti quel parametro dovrebbe essere un attributo di un'istanza.

2) Le regole per OO sono abbastanza ben comprese. Dopo un po ', puoi osservare il design di una classe e vedere se soddisfa i criteri come SOLID. E dopo un sacco di test di unità di pratica, si sviluppa un buon senso di ciò che rende una classe "di misura giusta" e "coerente". Ma non ci sono buone regole per una classe con tutti i metodi statici, e nessuna vera ragione per cui non dovresti semplicemente raggruppare tutto lì dentro. La classe è aperta nel tuo editor, quindi che diamine? Basta aggiungere il tuo nuovo metodo lì. Dopo un po ', la tua domanda si trasforma in una serie di "Oggetti di Dio" in competizione, ognuno dei quali cerca di dominare il mondo. Di nuovo, refactoring in unità più piccole è altamente soggettiva e difficile da dire se hai ragione.

3) Le interfacce sono una delle funzionalità più potenti di Java. L'ereditarietà delle classi si è dimostrata problematica, ma la programmazione con le interfacce rimane uno dei trucchi più potenti del linguaggio. (idem C #) Le classi tutte statiche non possono inserirsi in quel modello.

4) Sbatte le porte su importanti tecniche OO che non puoi sfruttare. Quindi puoi lavorare per anni con solo un martello nella tua cassetta degli attrezzi, senza rendersi conto di quanto sarebbero state più semplici se avessi avuto anche un cacciavite.

4.5) Crea le dipendenze in fase di compilazione più difficili e più infrangibili possibili. Quindi, ad esempio, se hai FileSystem.saveFile() , non c'è modo di cambiarlo, a meno di fingere la tua JVM in fase di esecuzione. Ciò significa che ogni classe che fa riferimento alla propria classe di funzioni statiche ha una dipendenza dura e in fase di compilazione su quella specifica implementazione, il che rende quasi impossibile l'estensione e complica enormemente i test. È possibile testare la classe statica in isolamento, ma diventa molto difficile testare le classi che fanno riferimento a quella classe in modo isolato.

5) Farai impazzire i tuoi colleghi di lavoro. La maggior parte dei professionisti con cui lavoro prendono sul serio il loro codice e prestano attenzione ad almeno alcuni principi di progettazione. Mettendo da parte l'intento di base di una lingua, li faranno tirar fuori i capelli perché continueranno a refactoring del codice.

Quando sono in una lingua, cerco sempre di usare bene una lingua. Quindi, per esempio, quando sono in Java, uso un buon design OO, perché in questo modo sto sfruttando il linguaggio per quello che fa. Quando sono in Python, mescolo funzioni a livello di modulo con classi occasionali - potrei solo scrivere classi in Python, ma poi penso che non userei il linguaggio per ciò che è buono.

Un'altra tattica è di usare male un linguaggio, e si lamentano di tutti i problemi che sta causando. Ma questo vale praticamente per qualsiasi tecnologia.

La caratteristica chiave di Java è la gestione della complessità in unità testate e di piccole dimensioni che sono collegate tra loro in modo che siano facili da comprendere. Java enfatizza le chiare definizioni dell'interfaccia indipendentemente dall'implementazione, il che rappresenta un enorme vantaggio. Questo è il motivo per cui (e altri linguaggi OO simili) rimangono così ampiamente utilizzati. Nonostante tutta la verbosità e il ritualismo, quando ho finito con una grande app Java, mi sento sempre come se le idee fossero più chiaramente separate nel codice rispetto ai miei progetti in un linguaggio più dinamico.

È difficile, però. Ho visto persone ricevere il bug "tutto statico", ed è piuttosto difficile discuterne. Ma li ho visti avere un grande senso di sollievo quando li superano.

    
risposta data 03.06.2014 - 23:43
fonte
6

Hai chiesto:

But are there any more advantages to objects over static classes?

Prima di questa domanda, hai elencato Polymorphism e passati intorno come due vantaggi dell'utilizzo degli oggetti. Voglio dire che quelle sono le caratteristiche del paradigma OO. L'incapsulamento è un'altra caratteristica del paradigma OO.

Tuttavia, quelli non sono i benefici. I vantaggi sono:

  1. Migliori astrazioni
  2. Migliore manutenibilità
  3. Migliore testabilità

Hai detto:

But recently when I stopped and thought about it, I realized that a static class would do just the same as an object, in a lot of the places where I normally use an object.

Penso che tu abbia un punto valido lì. Al suo centro, la programmazione non è altro che la trasformazione dei dati e la creazione di effetti collaterali basati sui dati. A volte la trasformazione dei dati richiede dati ausiliari. Altre volte no.

Quando si ha a che fare con la prima categoria di trasformazioni, i dati ausiliari devono essere passati come input o memorizzati da qualche parte. Un oggetto è l'approccio migliore delle classi statiche per tali trasformazioni. Un oggetto può memorizzare i dati ausiliari e usarli al momento giusto.

Per la seconda categoria di trasformazioni, una classe statica è valida come un oggetto, se non migliore. Le funzioni matematiche sono esempi classici di questa categoria. La maggior parte delle funzioni di libreria C standard rientrano anche in questa categoria.

Hai chiesto:

With an object, the calling line of code will look like this: Thing thing = fileLoader.load(file);

With a static class, it would look like this: Thing thing = FileLoader.load(file);

What's the difference?

Se FileLoader non ha, mai , è necessario memorizzare tutti i dati, vorrei andare con il secondo approccio. Se c'è una piccola possibilità che abbia bisogno di dati ausiliari per eseguire l'operazione, il primo approccio è una scommessa più sicura.

Hai chiesto:

Are there any more advantages to objects other from the two that I listed? Please explain.

Ho elencato i vantaggi (vantaggi) dell'uso del paradigma OO. Spero che si spieghino da soli. In caso contrario, sarò lieto di elaborare.

Hai chiesto:

But what about 'static' parts of the system - that aren't going to be passed around? For example - a 'save file' mechanism. Why should I implement it in an object, and not a static class?

Questo è un esempio in cui una classe statica semplicemente non lo farà. Esistono molti modi per salvare i dati dell'applicazione in un file:

  1. Salvalo in un file CSV.
  2. Salvalo in un file XML.
  3. Salvalo in un file json.
  4. Salvalo come file binario, con dump diretto dei dati.
  5. Salvalo direttamente in una tabella in un database.

L'unico modo per fornire tali opzioni è creare un'interfaccia e creare oggetti che implementano l'interfaccia.

In conclusione

Utilizza l'approccio giusto per il problema in questione. È meglio non essere religiosi su un approccio rispetto all'altro.

    
risposta data 04.06.2014 - 05:47
fonte
5

Dipende dalla lingua e dal contesto, a volte. Ad esempio, gli script PHP utilizzati per le richieste di assistenza hanno sempre una richiesta di assistenza e una risposta da generare per l'intera durata dello script, pertanto i metodi statici per agire sulla richiesta e generare una risposta potrebbero essere appropriati. Ma in un server scritto su Node.js , possono esserci molte richieste e risposte diverse tutte allo stesso tempo. Quindi la prima domanda è: sei sicuro che la classe statica corrisponda davvero a un oggetto singleton?

In secondo luogo, anche se disponi di singoletti, l'uso di oggetti ti consente di sfruttare il polimorfismo utilizzando tecniche come Dependency_injection e Factory_method_pattern . Questo è spesso usato in vari Inversion_of_control e utile per creare oggetti mock per test, logging, ecc.

Hai menzionato i vantaggi di cui sopra, quindi eccone uno che non hai: l'ereditarietà. Molte lingue non hanno la capacità di sovrascrivere i metodi statici nello stesso modo in cui i metodi di istanza possono essere sovrascritti. In generale, è molto più difficile ereditare e sovrascrivere i metodi statici.

    
risposta data 03.06.2014 - 23:55
fonte
3

Oltre al post di Rob Y

Finché la funzionalità di load(File file) può essere chiaramente separata da tutte le altre funzioni che usi, va bene usare un metodo / classe statico. Si esternalizza lo stato (ciò che non è una cosa negativa) e inoltre non si ottiene la ridondanza, poiché è possibile ad esempio applicare parzialmente o eseguire il curry della propria funzione in modo da non dover ripetere se stessi. (che è in realtà lo stesso o simile all'utilizzo di un modello factory )

Tuttavia, non appena due di queste funzioni iniziano ad avere un uso comune, si vuole essere in grado di raggrupparle in qualche modo. Immagina di avere non solo una funzione load ma anche una funzione hasValidSyntax . Che cosa farai?

     if (FileLoader.hasValidSyntax(myfile))
          Thing thing = FileLoader.load(myfile);
     else
          println "oh noes!"

Vedi i due riferimenti a myfile qui? Inizi a ripetere te stesso perché il tuo stato esternalizzato deve essere passato per ogni chiamata. Rob Y ha descritto come tu interiorizzi lo stato (qui file ) in modo che tu possa farlo in questo modo:

     FileLoader myfileLoader = new FileLoader(myfile)
     if (myfileLoader.hasValidSyntax())
          Thing thing = myfileLoader.load();
     else
          println "oh noes!"
    
risposta data 04.06.2014 - 01:23
fonte
2

Un grosso problema quando si usano le classi statiche è che costringono a nascondere le proprie dipendenze, e forzare a dipendere dalle implementazioni. Nella seguente firma del costruttore, quali sono le tue dipendenze:

public Person(String name)

Bene, a giudicare dalla firma, una persona ha solo bisogno di un nome, giusto? Bene, non se l'implementazione è:

public Person(String name) {
    ResultSet rs = DBConnection.getPersonFilePathByName(name);
    File f = FileLoader.load(rs.getPath());
    PersonData.setDataFile(f);
    this.name = name;
    this.age = PersonData.getAge();
}

Quindi una persona non è solo istanziata. Effettuiamo effettivamente il pull dei dati da un database, che ci fornisce un percorso per un file, che deve essere analizzato, e quindi i dati reali che vogliamo devono essere presi in considerazione. Questo esempio è chiaramente esagerato, ma dimostra il punto. Ma aspetta, ci può essere molto di più! Scrivo il seguente test:

public void testPersonConstructor() {
    Person p = new Person("Milhouse van Houten");
    assertEqual(10, p.age);
}

Questo dovrebbe passare però, giusto? Voglio dire, abbiamo incapsulato tutte le altre cose, giusto? Beh, in realtà esplode con un'eccezione. Perché? Oh, sì, le dipendenze nascoste di cui non eravamo a conoscenza hanno uno stato globale. Il DBConnection deve essere inizializzato e connesso. Il FileLoader deve essere inizializzato con un oggetto FileFormat (come XMLFileFormat o CSVFileFormat ). Mai sentito parlare di quelli? Bene, questo è il punto. Il tuo codice (e il tuo compilatore) non possono dirti che hai bisogno di queste cose perché le chiamate statiche nascondono quelle dipendenze. Ho detto test? Intendevo dire che il nuovo sviluppatore junior ha appena spedito qualcosa di simile nella tua versione più recente. Dopo tutto, compilato = funziona, giusto?

Inoltre, dì che sei su un sistema che non ha un'istanza MySQL in esecuzione. Oppure, supponiamo di essere su un sistema Windows ma la tua classe DBConnection esce solo sul tuo server MySQL su una macchina Linux (con percorsi Linux). In alternativa, supponiamo di essere su un sistema in cui il percorso restituito da DBConnection non è in lettura / scrittura per te. Ciò significa che provare a eseguire o testare questo sistema in una di queste condizioni non riuscirà, non a causa di un errore di codice, ma a causa di un errore di progettazione che limita la flessibilità del codice e ti lega a un'implementazione.

Ora, diciamo, vogliamo registrare ogni chiamata al database per una specifica istanza di Person , una che attraversa un certo percorso problematico. Potremmo inserire il logging in DBConnection , ma questo registrerà tutto , mettendo un sacco di confusione e rendendo difficile distinguere il particolare percorso del codice che vogliamo tracciare. Se, tuttavia, stiamo utilizzando l'iniezione di dipendenza con un'istanza DBConnection , potremmo semplicemente implementare l'interfaccia in un decoratore (o estendere la classe, poiché entrambe le opzioni sono disponibili con un oggetto). Con una classe statica, non possiamo iniettare la dipendenza, non possiamo implementare un'interfaccia, non possiamo avvolgerla in un decoratore e non possiamo estendere la classe. Possiamo solo chiamarlo direttamente, da qualche parte nascosto nel nostro codice. Quindi siamo forzati per avere una dipendenza nascosta su un'implementazione.

È sempre male? Non necessariamente, ma potrebbe essere meglio invertire il tuo punto di vista e dire "C'è una buona ragione per cui non dovrebbe essere un'istanza?" piuttosto che "C'è una buona ragione per cui dovrebbe essere un'istanza?" Se puoi davvero dire che il tuo codice sarà stabile (e senza stato) come Math.abs() , sia nella sua implementazione che nel modo in cui verrà usato, allora puoi considerare di renderlo statico. Avere istanze su classi statiche, però, ti dà un mondo di flessibilità che non è sempre facile da riacquistare dopo il fatto. Inoltre, può darti più chiarezza sulla vera natura delle dipendenze del tuo codice.

    
risposta data 05.06.2014 - 08:02
fonte
1

Solo i miei due centesimi.

Per la tua domanda:

But what about 'static' parts of the system - that aren't going to be passed around? For example - a 'save file' mechanism. Why should I implement it in an object, and not a static class?

Puoi chiederti se il meccanismo della parte del sistema che riproduce i suoni o la parte del sistema che salva i dati in un file CAMBIA . Se la tua risposta è sì, ciò indica che devi astrarli utilizzando abstract class / interface . Potresti chiedere, come posso sapere le cose future? Sicuramente, non possiamo. Quindi, se la cosa è stateless, puoi usare 'static class' come java.lang.Math , altrimenti usa un approccio orientato agli oggetti.

    
risposta data 04.06.2014 - 03:45
fonte

Leggi altre domande sui tag