Perché istanziare un oggetto in una classe base piuttosto che in una sottoclasse specifica?

8

Ad esempio:

URL blogFeedUrl = new URL("http://manishmaharzan.com.np/getJSON/json.json");
HttpURLConnection connection = (HttpURLConnection) blogFeedUrl.openConnection();
connection.connect();
InputStream inputStream = connection.getInputStream();
Reader reader = new InputStreamReader(inputStream);
int contentLength = connection.getContentLength();
char[] charArray = new char[contentLength];
reader.read(charArray);
String responseData = new String(charArray);

Qui avremmo potuto creare InputStreamReader invece per la classe genitore Reader :

InputStreamReader reader = new InputStreamReader(inputStream);

O per un altro esempio durante la creazione di SQLiteOpenHelper in un'attività:

public class Helper extends SQLiteOpenHelper
{
    …
}

public class DrinkActivity extends Activity
{
   protected void onCreate(…) { … }
   SQLiteOpenHelper helper = new Helper(this);
   …
}

Invece di fare riferimento alla classe base SQLiteOpenHelper qui, perché non creare

Helper helper = new Helper(this);

dopo tutto Helper estende SQLiteOpenHelper .

Qual è il vantaggio di codificarlo in questo modo?

    
posta mhrzn 27.03.2016 - 14:20
fonte

4 risposte

9

In parole semplici:

Dichiarando la variabile con il tipo di superclasse (cioè Reader o SQLiteOpenHelper ) assicuri che il codice che usa quell'oggetto funzionerà anche se lo installi in un diverso tipo di Reader o un diverso tipo di SQLiteOpenHelper fintanto che sono una sottoclasse di Reader o SQLiteOpenHelper rispettivamente. Il codice dovrebbe funzionare correttamente se si passa a FileReader o a StringReader .

Un confronto reale:

Quando impari a guidare una macchina di trasmissione manuale , puoi guidare quasi tutte le auto con cambio manuale. Immagina quanto sarebbe male se dovessi imparare a guidare ogni diverso modello di auto che incontri.

Quello che hai imparato è astratto: cambiare marcia, usare la frizione, ecc. Si applica a molti modelli di auto purché rispettino quel modo di lavorare. Essendo astratti e non dipendenti da nessun particolare modello di auto, le tue abilità di guida sono più utili.

Questa non è una coincidenza. I produttori di automobili progettano le loro auto per soddisfare ciò che ti aspetti. Ogni auto è diversa, alcune hanno campane e fischietti, ma avendo una frizione, un volante (invece di un joystick) e avendo la frizione, il freno e il pedale dell'acceleratore nelle posizioni che ti aspetti, sfruttano il fatto che la maggior parte degli utenti imparato a guidare una "macchina di trasmissione manuale" astratta. Alcune macchine hanno sterzo assistito, altre hanno freni ABS ma quei dettagli sono trasparenti per te. Non devi saperlo per poter utilizzare le abilità generali su come guidare.

Quando scrivi codice in base a Reader , stai insegnando i trucchi del codice che sono più utili. Sarà in grado di gestire qualsiasi Reader . Se esegui il codice per un'implementazione specifica, ad esempio InputStreamReader , il tuo codice è meno utile, perché sicuramente finirai per chiamare i metodi che esistono solo in quella specifica implementazione. Se è quello che devi fare, così sia, ma se non è così, programmando la più generale (astratta) classe (dichiarando le tue variabili in questo modo) il tuo codice ne trarrà beneficio e sarà più flessibile.

    
risposta data 28.03.2016 - 20:46
fonte
8

Principalmente ciò è dovuto al principio di segregazione dell'interfaccia . Vuoi che il tuo codice cliente dipenda da un'interfaccia tanto piccola quanto necessario, e ciò significa la classe più astratta. Questo isola il tuo codice qui dalle modifiche nelle classi derivate di cui non ti interessa.

Un altro vantaggio è se commetti un errore e instanzia una classe che non implementa effettivamente l'interfaccia, o forse quel codice viene modificato in seguito in modo che non implementa più l'interfaccia, l'errore del compilatore ti indicherà la linea in cui è dichiarato, piuttosto che in seguito, dove ha meno senso.

Si tratta di adottare scelte di stile che riducano al minimo la possibilità di introdurre errori e di trovare facilmente quegli errori quando li realizzi.

    
risposta data 28.03.2016 - 00:17
fonte
1

Fornire una risposta meno "accademica": limita la portata che è sempre buona. Invia un messaggio al programmatore successivo che esaminerà il codice che lo scopo dell'oggetto lettore è quello di ottenere i dati e gli aspetti tecnici (dettagli di implementazione) della classe derivata non hanno importanza. È il polimorfismo che rende più semplice seguire la logica di base del codice.

Se si vede che l'istanza della classe figlio assegnata a una variabile della classe genitore rende improvvisamente la cosa più semplice perché si sa che non si dovrà considerare alcuna delle funzionalità aggiunte dell'oggetto figlio. Filtra il rumore per te e ti aiuta a concentrarti su ciò che è rilevante.

    
risposta data 29.03.2016 - 00:05
fonte
1

Mi piace l'esempio di List di questo concetto.

Supponiamo che tu crei un metodo che elabora e utilizza internamente un ArrayList che è un'implementazione specifica dell'interfaccia List . Quindi restituisci un riferimento a ArrayList e tutto va bene.

Poi un mese dopo cambi l'implementazione del metodo perché risulta che ArrayList ti trattiene. Si desidera aggiungere un elemento al centro dell'elenco e ArrayList è troppo lento. A LinkedList è una struttura dati migliore per questo. Quindi ora devi cambiare il tuo tipo di ritorno in LinkedList e l'utente del tuo metodo deve cambiare anche il loro codice chiamante.

Se hai appena restituito un riferimento all'interfaccia List , puoi modificare l'implementazione senza modificare l'API.

    
risposta data 29.03.2016 - 02:41
fonte

Leggi altre domande sui tag