Diciamo che volevo creare un Java List<String>
(vedi specifica ) implementazione che utilizza un sottosistema complesso, ad esempio un database o un file system, per il proprio archivio in modo che funzioni come una raccolta permanente anziché in memoria.
Quindi ecco un'implementazione di scheletro:
class DbBackedList implements List<String> {
private DbBackedList() {}
/** Returns a list, possibly non-empty */
public static getList() { return new DbBackedList(); }
public String get(int index) {
return Db.getTable().getRow(i).asString(); // may throw DbExceptions!
}
// add(String), add(int, String), etc. ...
}
Il mio problema sta nel fatto che l'API DB sottostante potrebbe incontrare errori di connessione che non sono specificati in Interfaccia elenco che dovrebbe generare.
Il mio problema è se questo viola il Principio di sostituzione di Liskov (LSP).
Nel suo articolo su LSP , Bob Martin fornisce in realtà un esempio di PersistentSet che viola LSP. La differenza è che il suo Exception
appena specificato è determinato dal valore inserito e quindi sta rafforzando la precondizione. Nel mio caso l'errore di connessione / lettura è imprevedibile e dovuto a fattori esterni e quindi non è tecnicamente una nuova precondizione, semplicemente un errore di circostanza, forse come OutOfMemoryError che può verificarsi anche se non specificato.
In circostanze normali, il nuovo errore / eccezione potrebbe non essere mai lanciato. (Il chiamante potrebbe intercettare se è a conoscenza della possibilità, proprio come un programma Java limitato alla memoria potrebbe catturare in modo specifico OOME.)
Questo è quindi un argomento valido per lanciare un errore extra e posso comunque affermare di essere un java.util.List
valido (o scegliere il tuo SDK / lingua / collezione in generale) e non in violazione di LSP?
Modifica: questo argomento potrebbe essere più accettabile se consideri un FileBackedList
(più affidabile "connessione") piuttosto che un DbBackedList
.
Se questo viola effettivamente LSP e quindi non è praticamente utilizzabile, ho fornito due soluzioni alternative meno appetibili come risposte che puoi commentare, vedi sotto.
Nota: casi d'uso
Nel caso più semplice, l'obiettivo è fornire un'interfaccia familiare per i casi in cui (diciamo) un database viene usato come un elenco persistente e consentire operazioni List regolari come ricerca, sottolista e iterazione.
Un altro caso d'uso più avventuroso è una sostituzione di slot per le librerie che funzionano con gli elenchi di base, ad esempio se abbiamo una coda di attività di terze parti che di solito funziona con un elenco semplice:
new TaskWorkQueue(new ArrayList<String>()).start()
che è suscettibile di perdere tutta la coda in caso di arresto anomalo, se sostituiamo semplicemente questo con:
new TaskWorkQueue(new DbBackedList()).start()
otteniamo una persistenza istantanea e la possibilità di condividere le attività tra più di una macchina.
In entrambi i casi, potremmo gestire le eccezioni di connessione / lettura lanciate, magari riprovare la connessione / leggere prima o consentire loro di lanciare e arrestare il programma (ad es. se non possiamo modificare il codice TaskWorkQueue).