Comprensione della "programmazione di un'interfaccia"


30

Mi sono imbattuto molto nel termine "programmazione su un'interfaccia anziché in un'implementazione" e penso di capire cosa significhi. Ma voglio essere sicuro di capire i suoi benefici e le sue possibili implementazioni.

"Programmare su un'interfaccia" significa che, quando possibile, si dovrebbe fare riferimento a un livello più astratto di una classe (un'interfaccia, una classe astratta o talvolta una superclasse di qualche tipo), anziché fare riferimento a un'implementazione concreta.

Un esempio comune in Java è l'uso di:

List myList = new ArrayList();invece di ArrayList myList = new ArrayList();.

Ho due domande al riguardo:

  1. Voglio essere sicuro di comprendere i principali vantaggi di questo approccio. Penso che i benefici siano soprattutto la flessibilità. Dichiarare un oggetto come riferimento di più alto livello, piuttosto che come implementazione concreta, consente una maggiore flessibilità e manutenibilità durante tutto il ciclo di sviluppo e in tutto il codice. È corretto? La flessibilità è il vantaggio principale?

  2. Esistono altri modi di "programmare un'interfaccia"? Oppure "dichiarare una variabile come interfaccia piuttosto che come implementazione concreta" è l'unica implementazione di questo concetto?

Non sto parlando dell'interfaccia costrutto Java . Sto parlando del principio OO "programmazione di un'interfaccia, non di un'implementazione". In questo principio, "interfaccia" mondiale si riferisce a qualsiasi "supertipo" di una classe : un'interfaccia, una classe astratta o una semplice superclasse che è più astratta e meno concreta delle sue sottoclassi più concrete.




1
Questa risposta ti fornisce un esempio di facile comprensione programmers.stackexchange.com/a/314084/61852
Tulains Córdova

Risposte:


46

"Programmare su un'interfaccia" significa che, quando possibile, si dovrebbe fare riferimento a un livello più astratto di una classe (un'interfaccia, una classe astratta o talvolta una superclasse di qualche tipo), anziché fare riferimento a un'implementazione concreta.

Questo non è corretto . O almeno, non è del tutto corretto.

Il punto più importante viene dal punto di vista della progettazione del programma. Qui, "programmare su un'interfaccia" significa focalizzare la progettazione su ciò che il codice sta facendo, non su come lo fa. Questa è una distinzione vitale che spinge il tuo design verso correttezza e flessibilità.

L'idea principale è che i domini cambiano molto più lentamente rispetto al software. Supponi di avere un software per tenere traccia della tua lista della spesa. Negli anni '80, questo software avrebbe funzionato contro una riga di comando e alcuni file flat su floppy disk. Quindi hai un'interfaccia utente. Quindi potresti mettere la lista nel database. In seguito potrebbe essere passato al cloud o ai telefoni cellulari o all'integrazione di Facebook.

Se si progettasse il proprio codice specificamente attorno all'implementazione (floppy disk e righe di comando) si sarebbe mal preparati per le modifiche. Se hai progettato il codice sull'interfaccia (manipolando un elenco di generi alimentari), l'implementazione è libera di cambiare.


Grazie per la risposta. A giudicare da ciò che hai scritto, penso di capire cosa significhi "programmare su un'interfaccia" e quali siano i suoi vantaggi. Ma ho una domanda: l'esempio concreto più comune per questo concetto è questo: quando si crea un riferimento a un oggetto, impostare il tipo di riferimento sul tipo di interfaccia implementato da questo oggetto (o la superclasse che questo oggetto eredita), invece di creare il tipo di riferimento il tipo di oggetto. (Alka, List myList = new ArrayList()invece di ArrayList myList = new ArrayList(). (La domanda è nel prossimo commento)
Aviv Cohn

La mia domanda è: puoi darmi altri esempi di luoghi nel codice del mondo reale in cui si svolge il principio "programmazione a un'interfaccia"? A parte l'esempio comune che ho descritto nell'ultimo commento?
Aviv Cohn,

6
NO . List/ ArrayListnon è affatto quello di cui sto parlando. È più come fornire un Employeeoggetto piuttosto che l'insieme di tabelle collegate utilizzate per archiviare un record Employee. O fornire un'interfaccia per scorrere le canzoni e non preoccuparsi se tali canzoni vengono mescolate, su un CD o in streaming da Internet. Sono solo una sequenza di canzoni.
Telastyn,

1
L'esistenza del "pulsante turbo": en.wikipedia.org/wiki/Turbo_button è un esempio reale del mondo dell'analogia del floppy disk.
JimmyJames,

1
@AvivCohn Vorrei suggerire SpringFramework come un buon esempio. Tutto in primavera può essere migliorato o personalizzato creando la propria impementazione delle loro interfacce e il comportamento principale di Springs che le funzionalità continueranno a funzionare come previsto ... O in caso di personalizzazioni, come previsto. La programmazione su un'interfaccia è anche la migliore strategia per progettare quadri di integrazione . Ancora una volta la primavera fa con la sua integrazione primaverile. In fase di progettazione, la programmazione su un'interfaccia è ciò che facciamo con UML. O è quello che vorrei fare. Estrarre me stesso da come funziona e concentrarsi su cosa fare.
Laiv

19

La mia comprensione della "programmazione su un'interfaccia" è diversa da quella suggerita dalla domanda o dalle altre risposte. Il che non vuol dire che la mia comprensione sia corretta o che le cose nelle altre risposte non siano buone idee, solo che non sono ciò a cui penso quando sento quel termine.

Programmare su un'interfaccia significa che quando ti viene presentata qualche interfaccia di programmazione (che si tratti di una libreria di classi, un insieme di funzioni, un protocollo di rete o qualsiasi altra cosa) continui a utilizzare solo le cose garantite dall'interfaccia. Potresti avere conoscenze sull'implementazione sottostante (potresti averlo scritto), ma non dovresti mai usare quella conoscenza.

Ad esempio, supponiamo che l'API ti presenti un valore opaco che è un "handle" per qualcosa di interno. Le tue conoscenze potrebbero dirti che questo handle è davvero un puntatore e potresti dereferenziarlo e accedere ad un valore, il che potrebbe permetterti di svolgere facilmente alcune attività che vuoi fare. Ma l'interfaccia non ti offre questa opzione; è la tua conoscenza della particolare implementazione che lo fa.

Il problema è che crea un forte accoppiamento tra il codice e l'implementazione, esattamente ciò che l'interfaccia avrebbe dovuto impedire. A seconda della politica, ciò potrebbe significare che l'implementazione non può più essere modificata, perché ciò spezzerebbe il tuo codice, o che il tuo codice è molto fragile e continua a rompersi ad ogni aggiornamento o modifica dell'implementazione sottostante.

Un grande esempio di ciò sono i programmi scritti per Windows. WinAPI è un'interfaccia, ma molte persone hanno usato trucchi che hanno funzionato a causa della particolare implementazione in, dicono Windows 95. Questi trucchi forse hanno reso i loro programmi più veloci o hanno permesso loro di fare cose con meno codice di quanto sarebbe altrimenti necessario. Ma questi trucchi significano anche che il programma si arresterebbe in modo anomalo su Windows 2000, perché l'API era implementata diversamente lì. Se il programma fosse abbastanza importante, Microsoft potrebbe effettivamente andare avanti e aggiungere un po 'di hack alla sua implementazione in modo che il programma continui a funzionare, ma il costo di ciò è una maggiore complessità (con tutti i problemi che ne conseguono) del codice di Windows. Inoltre, rende la vita estremamente difficile per le persone di Wine, perché cercano di implementare anche WinAPI, ma possono fare riferimento alla documentazione solo per come farlo,


Questo è un buon punto, e lo sento molto in determinati contesti.
Telastyn,

Vedo il tuo punto. Fammi vedere se posso adattare ciò che stai dicendo alla programmazione generale: supponiamo che io abbia una classe (classe A) che utilizza funzionalità di classe astratta B. Le classi C e D ereditano la classe B - forniscono un'implementazione concreta per ciò che si dice che la classe faccia. Se la classe A utilizza direttamente la classe C o D, si chiama "programmazione per un'implementazione", che non è una soluzione molto flessibile. Ma se la classe A utilizza un riferimento alla classe B, che può essere successivamente impostata sull'implementazione C o sull'implementazione D, rende le cose più flessibili e gestibili. È corretto?
Aviv Cohn,

Se questo è corretto, la mia domanda è: ci sono esempi più concreti di "programmazione su un'interfaccia", oltre all'esempio comune "utilizzo di un riferimento all'interfaccia piuttosto che un riferimento di classe concreta"?
Aviv Cohn,

2
@AvivCohn Un po 'in ritardo in questa risposta, ma un esempio concreto è il world wide web. Durante le guerre dei browser (IE 4 era) i siti Web non venivano scritti in base a quanto indicato da una specifica, ma alle stranezze di alcuni browser (Netscape o IE). Fondamentalmente si stava programmando l'implementazione invece dell'interfaccia.
Sebastian Redl,

9

Posso solo parlare della mia esperienza personale, dato che nemmeno questo mi è mai stato insegnato formalmente.

Il tuo primo punto è corretto. La flessibilità acquisita deriva dal fatto di non essere in grado di invocare accidentalmente dettagli di implementazione della classe concreta in cui non dovrebbero essere invocati.

Ad esempio, considera ILoggerun'interfaccia che è attualmente implementata come una LogToEmailLoggerclasse concreta . La LogToEmailLoggerclasse espone tutti i ILoggermetodi e le proprietà, ma ha anche una proprietà specifica dell'implementazione sysAdminEmail.

Quando il tuo logger viene utilizzato nella tua applicazione, non dovrebbe essere la preoccupazione del codice che consuma per impostare il sysAdminEmail. Questa proprietà deve essere impostata durante l'installazione del logger e deve essere nascosta al mondo.

Se si stava eseguendo la codifica rispetto all'implementazione concreta, è possibile impostare accidentalmente la proprietà dell'implementazione quando si utilizza il logger. Ora, il codice dell'applicazione è strettamente accoppiato al tuo logger e il passaggio a un altro logger richiederà prima il disaccoppiamento del codice da quello originale.

In questo senso, la codifica su un'interfaccia allenta l'accoppiamento .

Per quanto riguarda il tuo secondo punto: un altro motivo che ho visto per la codifica di un'interfaccia è quello di ridurre la complessità del codice.

Per esempio, immaginate ho un gioco con le seguenti interfacce I2DRenderable, I3DRenderable, IUpdateable. Non è raro che un singolo componente del gioco abbia contenuti renderizzabili sia in 2D che in 3D. Altri componenti possono essere solo 2D e altri solo 3D.

Se il rendering 2D viene eseguito da un modulo, ha senso mantenere una raccolta di I2DRenderables. Non importa se gli oggetti nella sua collezione sono anche I3DRenderableo IUpdateblecome altri moduli saranno responsabili del trattamento di quegli aspetti degli oggetti.

La memorizzazione degli oggetti renderizzabili come un elenco I2DRenderablemantiene bassa la complessità della classe di rendering. La logica di rendering e aggiornamento 3D non è una delle sue preoccupazioni e quindi quegli aspetti dei suoi oggetti figlio possono e devono essere ignorati.

In questo senso, la codifica di un'interfaccia mantiene bassa la complessità isolando le preoccupazioni .


4

Ci sono forse due usi della parola interfaccia usata qui. L'interfaccia a cui ti riferisci principalmente nella tua domanda è un'interfaccia Java . Questo è specificamente un concetto Java, più in generale è un'interfaccia del linguaggio di programmazione.

Direi che programmare su un'interfaccia è un concetto più ampio. Le API REST ora disponibili che sono disponibili per molti siti Web sono un altro esempio del concetto più ampio di programmazione a un'interfaccia di livello superiore. Creando un livello tra il funzionamento interno del tuo codice e il mondo esterno (persone su Internet, altri programmi, anche altre parti dello stesso programma) puoi cambiare qualsiasi cosa all'interno del tuo codice purché non cambi ciò che il mondo esterno è in attesa, laddove definito da un'interfaccia o contratto che si intende onorare.

Ciò ti offre quindi la flessibilità di riformattare il tuo codice interno senza dover dire tutte le altre cose che dipendono dalla sua interfaccia.

Significa anche che il tuo codice dovrebbe essere più stabile. Attenendosi all'interfaccia, non dovresti rompere il codice di altre persone. Quando devi davvero cambiare l'interfaccia, puoi rilasciare una nuova versione principale (da 1.abc a 2.xyz) dell'API che segnala che si stanno verificando cambiamenti nell'interfaccia della nuova versione.

Come sottolinea @Doval nei commenti su questa risposta, esiste anche una località di errori. Penso che tutto si riduca all'incapsulamento. Proprio come lo useresti per gli oggetti in un design orientato agli oggetti, anche questo concetto è utile anche a un livello superiore.


1
Un vantaggio che di solito viene trascurato è la località degli errori. Supponi di aver bisogno di una mappa e di implementarla usando un albero binario. Affinché ciò funzioni, le chiavi devono avere un po 'di ordinamento ed è necessario mantenere l'invariante che le chiavi "inferiori a" della chiave del nodo corrente si trovano nella sottostruttura sinistra, mentre quelle che sono "maggiori di" sono attive la sottostruttura giusta. Quando nascondi l'implementazione della mappa dietro un'interfaccia, se una ricerca della mappa non va a buon fine, sai che il bug deve essere nel modulo Mappa. Se esposto, il bug potrebbe trovarsi ovunque nel programma. Per me, questo è il vantaggio principale.
Doval,

4

Un'analogia con il mondo reale potrebbe aiutare:

Una spina elettrica di rete in un'interfaccia.
Sì; quella cosa a tre pin all'estremità del cavo di alimentazione dalla TV, dalla radio, dall'aspirapolvere, dalla lavatrice, ecc.

Qualsiasi apparecchio che abbia una spina principale (cioè implementa l'interfaccia "ha una spina di rete") può essere trattato esattamente allo stesso modo; possono essere tutti collegati a una presa a muro e possono trarre energia da quella presa.

Ciò che ogni singolo apparecchio fa è completamente diverso. Non dovrai andare molto lontano a pulire i tappeti con la TV e la maggior parte delle persone non guarda la lavatrice per divertirsi. Ma tutti questi apparecchi condividono il comportamento comune di poter essere collegati a una presa a muro.

Ecco cosa ti danno le interfacce. Comportamenti
unificati che possono essere eseguiti da molte diverse classi di oggetti, senza la necessità delle complicazioni dell'ereditarietà.


1

Il termine "programmazione su un'interfaccia" è aperto a molte interpretazioni. L'interfaccia nello sviluppo del software è una parola molto comune. Ecco come spiego il concetto agli sviluppatori junior che ho addestrato negli anni.

Nell'architettura software ci sono molti limiti naturali. Esempi comuni includono

  • Il confine di rete tra i processi client e server
  • Il limite API tra un'applicazione e una libreria di terze parti
  • Confine di codice interno tra diversi domini aziendali all'interno del programma

Ciò che conta è che quando questi confini naturali esistono, vengono identificati e viene specificato il contratto su come si comporta quel confine. Si verifica il software non se si comporta "l'altra parte", ma se le interazioni corrispondono alla specifica.

Le conseguenze di ciò sono:

  • I componenti esterni possono essere scambiati purché implementino le specifiche
  • Punto naturale per i test unitari per convalidare il comportamento corretto
  • I test di integrazione diventano importanti: le specifiche erano ambigue?
  • Come sviluppatore hai un piccolo mondo di preoccupazione quando lavori su una determinata attività

Sebbene gran parte di ciò possa riguardare classi e interfacce, è altrettanto importante rendersi conto che si riferisce anche a modelli di dati, protocolli di rete e, in generale, lavorare con più sviluppatori

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.