L'accoppiamento con le stringhe è più "libero" rispetto ai metodi di classe?


18

Sto iniziando un progetto di gruppo scolastico in Java, usando Swing. È una GUI semplice sull'app desktop Database.

Il professore ci ha fornito il codice del progetto dell'anno scorso in modo da poter vedere come fa le cose. La mia impressione iniziale è che il codice sia molto più complicato di quanto dovrebbe essere, ma immagino che i programmatori lo pensino spesso quando guardano il codice che non hanno semplicemente scritto.

Spero di trovare ragioni per cui il suo sistema è buono o cattivo. (Ho chiesto al professore e ha detto che avrei visto in seguito perché è meglio, il che non mi soddisfa.)

Fondamentalmente, per evitare qualsiasi accoppiamento tra i suoi oggetti persistenti, i modelli (logica aziendale) e le viste, tutto viene fatto con le stringhe. Gli oggetti permanenti che vengono archiviati nel database sono hashtable di stringhe e i modelli e le viste "si iscrivono" tra loro, fornendo chiavi di stringa per gli "eventi" a cui si abbonano.

Quando viene attivato un evento, la vista o il modello invia una stringa a tutti i suoi abbonati che decidono cosa fare per quell'evento. Ad esempio, in uno dei metodi del listener di azioni per le viste (credo che questo imposti il ​​bicycleMakeField sull'oggetto persistente):

    else if(evt.getSource() == bicycleMakeField)
    {
        myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText());
    }

Quella chiamata alla fine arriva a questo metodo nel modello del veicolo:

public void stateChangeRequest(String key, Object value) {

            ... bunch of else ifs ...

    else
    if (key.equals("BicycleMake") == true)
    {
                ... do stuff ...

Il professore afferma che questo modo di fare le cose è più estensibile e mantenibile che avere la vista semplicemente chiamare un metodo su un oggetto di logica aziendale. Dice che non c'è accoppiamento tra punti di vista e modelli perché non conoscono l'esistenza reciproca.

Penso che questo sia un tipo peggiore di accoppiamento, perché la vista e il modello devono usare le stesse stringhe per funzionare. Se rimuovi una vista o un modello o esegui un refuso in una stringa, non otterrai errori di compilazione. Inoltre, rende il codice molto più lungo di quanto penso debba essere.

Voglio discuterne con lui, ma usa la sua esperienza nel settore per contrastare qualsiasi argomento che io, lo studente inesperto, potrei fare. C'è qualche vantaggio nel suo approccio che mi manca?


Per chiarire, voglio confrontare l'approccio di cui sopra, con la vista ovviamente abbinata al modello. Ad esempio, è possibile passare l'oggetto modello del veicolo alla vista, quindi, per modificare la "marca" del veicolo, procedere come segue:

vehicle.make = bicycleMakeField.getText();

Ciò ridurrebbe 15 righe di codice che sono attualmente utilizzate SOLO per impostare la marca del veicolo in un unico posto, a una singola riga di codice leggibile. (E poiché questo tipo di operazione viene eseguita centinaia di volte in tutta l'app, penso che sarebbe una grande vittoria per la leggibilità e la sicurezza.)


Aggiornare

Il mio team leader e io abbiamo ristrutturato il framework nel modo in cui volevamo farlo utilizzando la digitazione statica, informato il professore e abbiamo finito per dargli una demo. È abbastanza generoso da permetterci di usare il nostro framework fintanto che non gli chiediamo aiuto e se possiamo mantenere il resto della nostra squadra al passo con i tempi, il che ci sembra giusto.


12
OT. Penso che il tuo professore dovrebbe aggiornarsi e non usare il vecchio codice. Nel mondo accademico non c'è motivo di utilizzare una versione JAVA del 2006 quando è il 2012.
Farmor

3
Per favore, tienici aggiornati quando alla fine riceverai la sua risposta.
JeffO,

4
Indipendentemente dalla qualità del codice o dalla saggezza della tecnica, dovresti eliminare la parola "magia" dalla descrizione delle stringhe utilizzate come identificatori. "Stringhe magiche" implica che il sistema non è logico e colora la tua vista e avvelenerà qualsiasi discussione che hai con il tuo istruttore. Parole come "chiavi", "nomi" o "identificatori" sono più neutrali, forse più descrittive e hanno maggiori probabilità di tradursi in una conversazione utile.
Caleb,

1
Vero. Non le ho chiamate corde magiche mentre parlavo con lui, ma hai ragione, non c'è bisogno di farlo sembrare peggio di quello che è.
Filippo

2
L'approccio descritto dal tuo professore (notifica agli ascoltatori tramite eventi denominati stringhe) può essere buono. In realtà le enumerazioni potrebbero avere più senso, ma questo non è il problema più grande. Il vero problema che vedo qui è che l'oggetto che riceve la notifica è il modello stesso, e quindi devi inviarlo manualmente in base al tipo di evento. In una lingua in cui le funzioni sono di prima classe, dovresti registrare una funzione , non un oggetto a un evento. In Java, suppongo che si dovrebbe usare un qualche tipo di oggetto che avvolge una singola funzione
Andrea

Risposte:


32

L'approccio che il tuo professore propone è meglio descritto come tipizzato in modo rigoroso ed è sbagliato a quasi tutti i livelli.

L'accoppiamento è ridotto al meglio dall'inversione di dipendenza , che lo fa da un invio polimorfico, piuttosto che da un certo numero di casi.


3
Scrivi "quasi tutti i livelli", ma non hai scritto perché non è (secondo te) un caso adatto in questo esempio. Sarebbe interessante saperlo.
Hacre,

1
@hakre: non ci sono casi adatti, almeno non in Java. Ho detto che "è sbagliato su quasi tutti i livelli", perché è meglio che non usare alcuna indiretta e lanciare tutto il codice in un unico posto. Tuttavia l'uso delle costanti di stringa magiche (globali) come base per un invio manuale è solo un modo contorto di usare oggetti globali alla fine.
back2dos,

Quindi il tuo argomento è: non ci sono mai casi adatti, quindi anche questo non è uno? Non è certo un argomento e in realtà non è molto utile.
Hakre,

1
@hakre: La mia argomentazione è che questo approccio è negativo per una serie di ragioni (paragrafo 1 - ragioni sufficienti per evitarlo sono fornite nella spiegazione delle stringhe) e non dovrebbero essere usate, perché ce n'è una migliore (paragrafo 2 ).
back2dos,

3
@hakre Posso immaginare una situazione che giustificherebbe questo approccio se un serial killer ti avesse attaccato alla tua sedia e ti tenesse una motosega sopra la testa, ridendo maniacalmente e ordinandoti di usare letterali di stringa ovunque per la logica di spedizione. A parte questo, è preferibile fare affidamento sulle funzionalità del linguaggio per fare questo tipo di cose. In realtà, riesco a pensare a un altro caso che coinvolge coccodrilli e ninja, ma è un po 'più coinvolto.
Rob,

19

Il tuo professore sta sbagliando . Sta cercando di disaccoppiare completamente la vista e il modello forzando la comunicazione a viaggiare via stringa in entrambe le direzioni (la vista non dipende dal modello e il modello non dipende dalla vista) , il che sconfigge totalmente lo scopo del design orientato agli oggetti e MVC. Sembra che invece di scrivere lezioni e progettare un'architettura basata su OO, stia scrivendo moduli disparati che rispondono semplicemente a messaggi basati su testo.

Per essere in qualche modo corretto con il professore, sta cercando di creare in Java qualcosa di molto simile all'interfaccia .NET molto comune chiamata INotifyPropertyChanged, che notifica agli abbonati gli eventi di modifica mediante il passaggio di stringhe che specificano quale proprietà è cambiata. Almeno in .NET, questa interfaccia viene utilizzata principalmente per comunicare da un modello di dati per visualizzare oggetti ed elementi della GUI (associazione).

Se fatto bene , adottare questo approccio può avere alcuni vantaggi¹:

  1. La vista dipende dal modello, ma il modello non deve dipendere dalla vista. Questa è l' inversione di controllo appropriata .
  2. Hai un unico posto nel tuo codice View che gestisce la risposta alle notifiche di modifica dal modello e un solo posto per iscriverti / annullare l'iscrizione, riducendo la probabilità di una perdita di memoria di riferimento sospesa.
  3. Esiste un modo per ridurre il sovraccarico di codifica quando si desidera aggiungere o rimuovere un evento dall'editore dell'evento. In .NET, esiste un meccanismo incorporato di pubblicazione / sottoscrizione chiamato eventsma in Java è necessario creare classi o oggetti e scrivere il codice di sottoscrizione / cancellazione per ciascun evento.
  4. Il principale svantaggio di questo approccio è che il codice di abbonamento può non essere sincronizzato con il codice di pubblicazione. Tuttavia, questo è anche un vantaggio in alcuni ambienti di sviluppo. Se nel modello viene sviluppato un nuovo evento e la vista non è ancora stata aggiornata per abbonarsi, la vista probabilmente funzionerà comunque. Allo stesso modo, se un evento viene rimosso, hai del codice morto, ma potrebbe comunque essere compilato ed eseguito.
  5. Almeno in .NET, qui Reflection viene utilizzato in modo molto efficace per chiamare automaticamente getter e setter in base alla stringa che è stata passata al sottoscrittore.

Quindi, in breve (e per rispondere alla tua domanda), può essere fatto, ma l'approccio che il tuo professore sta adottando è in colpa per non consentire almeno una dipendenza a senso unico tra la vista e il modello. Non è solo pratico, eccessivamente accademico e non è un buon esempio di come usare gli strumenti a portata di mano.


¹ Cerca su Google per INotifyPropertyChangedtrovare un milione di modi in cui le persone trovano per evitare di usare la corda magica durante l'implementazione.


Sembra che tu abbia descritto SOAP qui. : D… (ma sì, ho capito bene e hai ragione)
Konrad Rudolph,

11

enums (o public static final Strings) dovrebbero essere usati al posto delle stringhe magiche.

Il tuo professore non capisce OO . Ad esempio avere una classe di veicoli concreta?
E far comprendere a questa classe la marca di una bicicletta?

Il veicolo dovrebbe essere astratto e dovrebbe esserci una sottoclasse di cemento chiamata Bike. Vorrei seguire le sue regole per ottenere un buon voto e poi dimenticare il suo insegnamento.


3
A meno che il punto di questo esercizio non sia migliorare il codice usando principi OO migliori. In tal caso, rifrattore via.
joshin4colours,

2
@ joshin4colours Se StackExchange lo avesse valutato, avresti ragione; ma dal momento che il selezionatore è la stessa persona che ha avuto l'idea che avrebbe dato a tutti noi brutti voti perché sapeva che la sua implementazione era migliore.
Dan Neely,

7

Penso che tu abbia un buon punto, le corde magiche sono cattive. Qualcuno nella mia squadra ha dovuto eseguire il debug di un problema causato da stringhe magiche un paio di settimane fa (ma era nel loro codice che hanno scritto, quindi ho tenuto la bocca chiusa). Ed è successo ... nell'industria !!

Il vantaggio del suo approccio è che potrebbe essere più veloce da programmare, specialmente per piccoli progetti demo. Gli svantaggi sono che è incline a errori di battitura e più spesso viene utilizzata la stringa, maggiori sono le possibilità che un codice di errore comporti errori. E se mai vuoi cambiare una stringa magica, il refactoring delle stringhe è più difficile del refactoring delle variabili poiché gli strumenti di refactoring potrebbero anche toccare stringhe che contengono la stringa magica (come sottostringa) ma non sono esse stesse la stringa magica e non dovrebbero essere toccate. Inoltre, potresti dover tenere un dizionario o un indice di stringhe magiche in modo che i nuovi sviluppatori non inizieranno a inventare nuove stringhe magiche per lo stesso scopo e non perderanno tempo a cercare stringhe magiche esistenti.

In questo contesto, sembra che un Enum potrebbe essere migliore delle stringhe magiche o persino delle costanti di stringhe globali. Inoltre, gli IDE ti daranno spesso una sorta di code-assist (completamento automatico, refactor, trova tutti i riferimenti, ecc ...) quando usi stringhe costanti o Enum. Un IDE non ti darà molto aiuto se usi stringhe magiche.


Concordo sul fatto che gli enum sono migliori dei letterali a stringa. Ma anche allora, è davvero meglio chiamare un "stateChangeRequest" con una chiave enum, o semplicemente chiamare direttamente un metodo sul modello? In entrambi i casi, il modello e la vista sono accoppiati in quel punto.
Filippo

@Philip: Beh, allora, immagino di disaccoppiare il modello e visualizzare a quel punto, potresti aver bisogno di una sorta di controller-classe per farlo ...
FrustratedWithFormsDesigner

@FrustratedWithFormsDesigner - Sì. Un'architettura MVC è la più disaccoppiata
cdeszaq

Bene, se un altro livello aiuterà a separarli, non direi nulla di contrario. Ma quel livello potrebbe ancora essere tipizzato staticamente e usare metodi reali piuttosto che stringhe o messaggi enum per collegarli.
Filippo

6

L'uso di "esperienza nel settore" è un argomento scarso. Il tuo professore ha bisogno di elaborare una discussione migliore di quella :-).

Come altri hanno già affermato, l'uso delle stringhe magiche è sicuramente disapprovato, l'uso di enum è considerato molto più sicuro. Un enum ha significato e portata , le stringhe magiche no. A parte questo, il modo in cui il tuo professore disaccoppia le preoccupazioni è considerato una tecnica più antica e più fragile di quella utilizzata oggi.

Vedo che @backtodos ha affrontato questo problema mentre rispondevo a questo, quindi aggiungerò semplicemente la mia opinione che usando Inversion of Control (di cui Dependency Injection è un modulo, ti imbatterai nel framework Spring nell'industria prima o poi a lungo. ..) è considerato un modo più moderno di affrontare questo tipo di disaccoppiamento.


2
Sono pienamente d'accordo che il mondo accademico dovrebbe avere requisiti molto più elevati rispetto al settore. The academia == miglior modo teorico; L'industria == miglior modo pratico
Farmor

3
Sfortunatamente, alcuni di quelli che insegnano queste cose nel mondo accademico lo fanno perché non riescono a tagliarle nell'industria. Sul lato positivo, alcuni di coloro che sono nel mondo accademico sono brillanti e non dovrebbero essere tenuti nascosti in alcuni uffici aziendali.
FrustratedWithFormsDesigner

4

Siamo abbastanza chiari; c'è inevitabilmente qualche accoppiamento lì dentro. Il fatto che esista un argomento così abbonabile è un punto di accoppiamento, così come la natura del resto del messaggio (come "stringa singola"). Detto questo, la domanda diventa quindi se l'accoppiamento sia maggiore se eseguito tramite stringhe o tipi. La risposta dipende dal fatto che tu sia preoccupato per l'accoppiamento che è distribuito nel tempo o nello spazio: se i messaggi verranno conservati in un file prima di essere letti in seguito, o verranno inviati a un altro processo (specialmente se che non è scritto nella stessa lingua), l'uso delle stringhe può rendere le cose molto più semplici. Il rovescio della medaglia è che non esiste un controllo del tipo; gli errori non verranno rilevati fino al runtime (e talvolta nemmeno in quel momento).

Una strategia di mitigazione / compromissione per Java può essere quella di utilizzare un'interfaccia tipizzata, ma in cui tale interfaccia tipizzata si trova in un pacchetto separato. Dovrebbe consistere solo di Java interfacee tipi di valori di base (ad es. Enumerazioni, eccezioni). Non ci dovrebbe essere alcun implementazioni di interfacce in quel pacchetto. Quindi tutti implementano ciò e, se è necessario trasmettere i messaggi in modo complesso, una particolare implementazione di un delegato Java può usare stringhe magiche se necessario. Inoltre, semplifica notevolmente la migrazione del codice in un ambiente più professionale come JEE o OSGi e aiuta notevolmente piccole cose come i test ...


4

Ciò che il tuo professore propone è l'uso di "corde magiche". Mentre fa "accoppiare vagamente" le classi tra loro, consentendo cambiamenti più facili, è un anti-schema; le stringhe dovrebbero essere molto, MOLTO raramente usate per contenere o controllare le istruzioni del codice, e quando deve assolutamente accadere, il valore delle stringhe che stai usando per controllare la logica dovrebbe essere definito centralmente e costantemente, quindi c'è UN'autorità sul valore atteso di qualsiasi stringa particolare.

Il motivo è molto semplice; le stringhe non sono controllate dal compilatore per la sintassi o la coerenza con altri valori (nella migliore delle ipotesi, sono controllate per la forma corretta per quanto riguarda i caratteri di escape e altra formattazione specifica della lingua all'interno della stringa). Puoi mettere tutto quello che vuoi in una stringa. Come tale, è possibile ottenere un algoritmo / programma irrimediabilmente rotto da compilare, e non è fino a quando non si verifica che si verifichi un errore. Considera il seguente codice (C #):

private Dictionary<string, Func<string>> methods;

private void InitDictionary() //called elsewhere
{
   methods = new Dictionary<string, Func<string>> 
      {{"Method1", ()=>doSomething()},
       {"MEthod2", ()=>doSomethingElse()}} //oops, fat-fingered it
}

public string RunMethod(string methodName)
{
   //very naive, but even a check for the key would generate a runtime error, 
   //not a compile-time one.
   return methods[methodName](); 
}

...

//this is what we thought we entered back in InitDictionary for this method...
var result = RunMethod("Method2"); //error; no such key

... tutto questo codice viene compilato, prima prova, ma l'errore è evidente con una seconda occhiata. Esistono numerosi esempi di questo tipo di programmazione e sono tutti soggetti a questo tipo di errore, ANCHE SE si definiscono le stringhe come costanti (poiché le costanti in .NET sono scritte nel manifest di ogni libreria in cui vengono utilizzate, che devono quindi tutti devono essere ricompilati quando il valore definito viene modificato in modo che il valore cambi "globalmente"). Poiché l'errore non viene rilevato al momento della compilazione, deve essere rilevato durante il test di runtime e ciò è garantito solo con una copertura del codice del 100% nei test unitari con un esercizio adeguato del codice, che di solito si trova solo nella più assoluta sicurezza assoluta , sistemi in tempo reale.


2

In realtà, quelle classi sono ancora strettamente accoppiate. Tranne ora, sono strettamente accoppiati in modo tale che il compilatore non può dirti quando le cose sono rotte!

Se qualcuno cambia "BicycleMake" in "BicyclesMake", nessuno scoprirà che le cose sono rotte fino al runtime.

Gli errori di runtime sono molto più costosi da correggere rispetto agli errori di compilazione: sono tutti i tipi di malvagità.

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.