Un'altra domanda "Perché usare Abstract / Interfaccia". Ma sono uno sviluppatore solista. Perché usarlo?

7

Conosco lo scopo e tutto. Vedo me stesso come sviluppatore solista per un paio di anni.

Vedo sempre le risposte che è un contratto. Sì, lo capisco.

Ma ecco qualcosa nella mia mente:

Se una classe non fornisce ciò che vuole un'interfaccia, genererà un errore.

Bene, se una classe ha davvero bisogno di quel metodo, genererà comunque un errore se c'è qualcosa che lo chiama e non è lì giusto?

Qual è la differenza?

Posso solo realizzarlo e andare avanti con le "norme", ma questo mi lascerà sospeso con la domanda "perché". Non mi piace seguire ciecamente qualcosa senza capirlo.

EDIT:

Ho provato a cercare risposte su questa domanda molte volte prima e quello che trovo sempre è qualcosa come "Così quando qualcun altro ...". Non ho provato a lavorare con qualcun altro prima e non sono sicuro se questo sia davvero il motivo per cui usi un'interfaccia.

Voglio dire, perché faccio tutto da solo così so che cosa nel mio codice ha bisogno giusto? E ancora, anche se dimentico di implementare un metodo, vedrò comunque un errore che dice che un metodo non è definito.

EDIT 2:

The Dependency Injection è un'ottima risposta. Implementare interfacce su questi aiuti nel caso in cui sia necessario sostituire le implementazioni di dipendenza. Sei in qualche modo sicuro che quello che ti serve è fornito.

È un po 'più chiaro per me ora che è un Contratto tra componenti (forse anche tra gli sviluppatori)

    
posta jen 03.04.2017 - 15:16
fonte

9 risposte

13

I always see answers that it is contract. Yes I get it.

I can just actually implement it and go along with the "norms" but that will leave me hanging with the question "why".

Se ti stai chiedendo perché, forse non lo capisci davvero.

L'uso delle interfacce è una tecnica di disaccoppiamento. Consentono che parte del codice sia ignorante dei dettagli di implementazione di un'altra parte del codice. Ciò ti consente di modificare i dettagli di implementazione in modo sicuro sapendo che le altre parti del tuo codice non subiscono alcun impatto.

Ciò riduce il carico cognitivo quando si apportano le modifiche. Ciò riduce lo sforzo di test necessario per assicurarsi che le cose funzionino correttamente dopo le modifiche. E rendono le cose molto più semplici quando hai bisogno di implementazioni multiple per coesistere allo stesso tempo.

Ma a volte non hai bisogno di questi benefici, quindi le interfacce sono solo in alto. Il modo migliore per imparare davvero questo tipo di impatto è scrivere codice. Scrivi alcuni con le interfacce. Scrivi alcuni senza. Guarda come il design si sposta con ciascuno di essi. Scopri come le modifiche al programma influiscono in modo diverso a seconda del progetto.

    
risposta data 03.04.2017 - 15:50
fonte
6

Se non ritieni di aver bisogno di un'interfaccia, probabilmente no. Usa la classe direttamente. Mantienilo semplice. Se un'interfaccia è implementata da una sola classe, allora è probabilmente un'interfaccia inutile.

Le interfacce sono utili in alcune circostanze particolari, come

  • consentire più implementazioni compatibili di alcuni servizi
  • ti consente di prendere in giro un componente a scopo di test
  • definizione dei contratti tra sottosistemi

Ma se non hai quei requisiti, allora non hai bisogno di interfacce.

I contratti (quando si prendono le interfacce) non sono contratti tra sviluppatori , sono contratti tra componenti . Il numero di sviluppatori è irrilevante, la domanda è se il sistema è abbastanza complesso che un tale disaccoppiamento fornisce valore.

    
risposta data 03.04.2017 - 17:00
fonte
2

Se, quando si scrivono i test unitari, si utilizza un framework di simulazione che creava mock basati sulla classe, non credo che sia necessaria un'interfaccia per una classe molto ben definita.

L'astrazione dovrebbe provenire dal refactoring

  • quando due classi avranno dettagli di implementazione comuni, potresti voler spostare quei bit in una classe astratta, per rispettare DRY principio.
  • quando due classi hanno lo stesso comportamento con diverse implementazioni, dovresti usare un'interfaccia per definire il comportamento e iniettare ogni implementazione, se necessario

In conclusione, sono d'accordo con te sul fatto che l'ottimizzazione e l'astrazione premature portano a un sovraingegnerizzazione (principio YAGNI ) .

Per quanto riguarda l'argomento "solista", in 2 anni sarai una persona diversa e ringrazierai il tuo attuale se hai mantenuto la base di codice mantenibile. Oppure potresti essere dispiaciuto per aver preso alcune decisioni.

    
risposta data 03.04.2017 - 15:32
fonte
0

Risposta a questo punto specifico:

I mean, because I do everything my own so I do know what something in my code needs right?

Solo perché hai scritto qualcosa non vuol dire che ricorderai sempre ciò di cui ha bisogno, specialmente dopo qualche anno su grandi basi di codice.

Probabilmente troverai più facile navigare rispetto a qualcuno che non l'ha scritto, ma ciò non significa che ne ricorderai ogni aspetto.

    
risposta data 03.04.2017 - 16:54
fonte
0

Le interfacce sono contratti e il 90% del tempo sono contratti con altre persone / tecnologie. Ma possono anche essere contratti con te stesso: il tuo io futuro.

Si supponga di voler trasferire un'applicazione su una nuova piattaforma che richiede di cambiare la posizione in cui si ricevono i dati, ma di supportare ancora la vecchia piattaforma. Oops, ora devi scrivere due diverse versioni del tuo codice di recupero dei dati e tutto il codice che lo utilizza. Oppure, basta usare un'interfaccia e caricare una classe diversa per recuperare i dati da una fonte diversa.

Questo è in realtà uno scenario del mondo reale che mi è successo: avevo un'app WPF esistente che utilizzava un database locale e funzionava su tablet (senza connessione dati) per i tecnici sul campo. Avevo bisogno di portarlo su UWP / iOS / Android in modo che funzionasse su telefoni e tablet connessi. Ma non potevo semplicemente abbandonare la vecchia versione e chiedere all'azienda di acquistare 100s di nuovi telefoni / tablet in un solo colpo. Da quando ho estratto il livello di accesso ai dati tramite un'interfaccia, ho semplicemente scritto nuove classi di accesso ai dati che hanno contattato un servizio piuttosto che un database locale e ho utilizzato DI per caricare la classe appropriata per l'ambiente.

    
risposta data 03.04.2017 - 19:23
fonte
0

Per quanto mi riguarda, la scuola fa un lavoro molto povero insegnando cose del genere. Il problema è che entrano significativamente in gioco solo quando i sistemi diventano più grandi.

Prendiamo un caso reale per mostrare il valore dell'astratto:

Ho qui un programma che, tra le altre cose, digerisce alcuni rapporti "cartacei" (vengono stampati su disco) e li trasforma in elenchi di check-in su schermo. Ci sono attualmente (conoscendo il mio capo, sarei molto sorpreso se la lista non crescesse ad un certo punto) una mezza dozzina di parser diversi per diverse possibili strutture di report e alcune di quelle strutture possono effettivamente avere più rapporti.

Devo scrivere una routine di checkoff separata per ogni tipo di rapporto? Ovviamente no! Tuttavia non esiste un "rapporto" standard, l'unico campo comune a ogni tipo di rapporto è la quantità.

Quindi ho un tipo astratto che rappresenta un report e implementa un gruppo di comportamenti comuni a tutti i report. Quindi ho una classe per ogni tipo di rapporto che sa come analizzare il suo rapporto e fornire i valori in risposta a stringhe contenenti i nomi dei campi desiderati.

C'è anche una stampante per etichette che prende le stesse stringhe e rilascia un'etichetta per attaccare sull'oggetto.

Sia il controllo schermo che la stampante per etichette funzionano interamente sul tipo di rapporto astratto (ovviamente è stato inizializzato come un tipo di discendente in risposta alle informazioni lette da un file di configurazione) e quasi mai devono essere aggiornati quando vengono aggiunti nuovi report al sistema.

Nella mia attuale lingua primaria, C #, le interfacce sono per quando è necessario definire un comportamento separato dalla classe che lo utilizza. Devo ancora trovare un motivo per scriverne uno ma considerare un sistema standard uno: IDisposable. Fondamentalmente ogni classe che controlla una risorsa limitata deve implementare questo per garantire una corretta pulizia.

    
risposta data 06.04.2017 - 00:56
fonte
-1

Una delle più importanti implicazioni del disaccoppiamento (che è lo scopo principale delle interfacce) non è solo la possibilità di modificare più facilmente il codice senza (o con meno) effetti collaterali (che è già enorme).

(Questa è non una descrizione esaustiva dei vantaggi delle interfacce!)

Inoltre puoi scrivere il tuo codice in modo più flessibile, con conseguente minor numero di codice (meno dichiarazioni IF, ecc.). Ciò è particolarmente vero se gestisci molti oggetti di classi diverse, forse anche in un contenitore / array.

Immagina il seguente scenario: il tuo programma ha classi per Cat, Dog, Car, Train, Wind. Vuoi avere una matrice o un elenco degli oggetti di queste classi ordinati in base alla loro velocità di movimento. Ora queste classi POTREBBERO derivare dalla stessa classe di base (es), perché hanno tutte una proprietà "velocità". La classe base potrebbe essere "MoveableObject". Ma potrebbero anche avere qualcos'altro in comune, ad esempio la possibilità di creare una stringa di registro (metodo: CreateLogString ()). Poiché tutte queste classi avranno questa capacità, sarà necessario creare un metodo virtuale "CreateLogString" nella classe hbase "MoveableObject". D'altra parte, questo costringe tutti gli oggetti mobili ad implementare CreateLogString e viceversa.

Per dichiarare "abilità" per le classi (come la possibilità di creare una stringa di log, o la possibilità di avere velocità), potresti implementare 2 interfacce: ILogstringAble, IHasSpeed (o comunque vuoi chiamarle).

Ora se implementi un metodo che accetta un oggetto e registra il suo valore corrente, puoi codificare quel metodo per ricevere un argomento di tipo ILogstringAble. Non è necessario sovraccaricare questo metodo per tutte queste classi.

void PrintLogs(ILogstringAble obj) {
    string s = obj.CreateLogString();
    ...do something...
    print(s);
}

Se hai una matrice di oggetti IHasSpeed, puoi ordinare quell'array usando la proprietà speed di IHasSpeed. Questo compito non sarebbe così facile se non si astragga la capacità di avere velocità usando quell'interfaccia.

In breve: con le interfacce è possibile spostare un livello di astrazione e "categorizzare" gli oggetti in base al loro comportamento comune, anche se si tratta di istanze di classi diverse.

    
risposta data 03.04.2017 - 16:42
fonte
-1

Trovo che lavorare con l'astrazione e le interfacce nelle fasi di progettazione e architettura mi aiuti a pensare di più su cosa sto cercando di realizzare piuttosto che come . Il risultato è un design relativamente indipendente dal linguaggio e dalla piattaforma e che può essere facilmente esteso a problemi simili che vedrò in futuro.

    
risposta data 03.04.2017 - 18:02
fonte
-3

Un'interfaccia in lingue come C # non è un contratto. È semplicemente una raccolta di metodi, proprietà e firme di eventi. Per essere un contratto, ognuno di questi membri avrebbe bisogno di una specifica (cioè precondizioni, post-condizioni), nonché di invarianti per l'astrazione nel suo insieme. Poiché in C # e in altre lingue .net mancano metodi formali per specificare i contratti (tranne gli strumenti incompleti forniti da Microsoft), possiamo creare solo contratti informali in tale lingua.

In .net o Java, l'unica ragione per utilizzare un'interfaccia è perché sei costretto a, a causa di alcuni requisiti dello strumento, o sei costretto a perché hai bisogno di ereditarietà multipla, e non c'è altro modo di farlo. Le interfacce non necessarie semplicemente ingombrano il codice e causano ulteriori errori umani.

Se senti la necessità di aggiungere interfacce superflue a causa della pressione dei colleghi, considera quanto segue:

  • Molti linguaggi di programmazione orientati agli oggetti non hanno il concetto di un'interfaccia, perché supportano l'ereditarietà multipla usando le classi. Potenziali problemi con l'ereditarietà multipla sono stati risolti nei linguaggi di programmazione molto prima che Java e .net venissero progettati. Il concetto di un'interfaccia, che è astrattamente identico a una classe vuota, può essere visto come soluzione alternativa a un difetto in un linguaggio di programmazione: nessun buon supporto per l'ereditarietà multipla.
  • Alcune persone sostengono che le interfacce forniscono "disaccoppiamento". Il codice che utilizza un'interfaccia è strongmente accoppiato a quell'interfaccia e anche una classe che implementa un'interfaccia è strongmente accoppiata a quell'interfaccia. Pertanto, un'interfaccia non è più "disaccoppiata" rispetto a una classe base nella stessa situazione.
  • Le interfacce non sono contratti (vedi sopra)
  • L'uso eccessivo di interfacce può promuovere l'ereditarietà della composizione, quando è preferibile la composizione. Una classe può implementare molte interfacce diverse, ma può ereditare solo da una classe base. Questo può portare a classi gonfiate. Una progettazione alternativa, utilizzando le classi di base anziché le interfacce e la promozione della composizione, avrebbe la stessa classe contenente istanze di altre classi, ognuna delle quali eredita da una classe base diversa.
  • Quando si crea una libreria riutilizzabile per gli altri, fornendo un'interfaccia invece di una classe base, si consente agli utenti della libreria di implementare tale interfaccia da qualsiasi classe, evitando restrizioni di ereditarietà. Questo può essere necessario e una buona cosa. D'altra parte, fornendo solo le classi base invece delle interfacce, è possibile promuovere la composizione sull'ereditarietà.
risposta data 04.04.2017 - 01:50
fonte

Leggi altre domande sui tag