Recentemente ho notato che l'ereditarietà gerarchica può essere una reliquia del pensare alle classi come "strutture con funzioni" piuttosto che a una mentalità legata al contratto di prodotto.
Considerare, come semplice esemplare, l'implementazione di "Iterator non modificabile" da Guava. link È un iteratore che genera un "UnsupportedOperationException" su invocazione di remove ().
Ora, sono sicuro che la maggior parte delle persone sarebbe d'accordo sul fatto che l'implementazione di un contratto e il fatto che uno dei suoi metodi genera sempre un'eccezione è una cattiva forma - quando si implementa un contratto si sta implicitamente garantendo che tutti i metodi funzionino. Eppure, quali sono le nostre opzioni qui? Potremmo dichiarare un'interfaccia che non contiene il metodo remove ma che renderebbe il nostro tipo restituito incompatibile con tutti i metodi che funzionano sugli iteratori. Potremmo incolpare i progettisti dell'API Java per forzare il metodo remove () a far parte di ogni iteratore, piuttosto che spostarlo su un'interfaccia di livello superiore come "RemovableIterator".
Se lo facessero, eviterebbero davvero alcuni problemi ma diciamo che abbiamo bisogno di un iteratore che possa anche impostare valori chiamati "SettableIterator" (implements setValue (T)) e anche un iteratore ripristinabile. Se richiediamo una combinazione di queste funzionalità, siamo costretti a dichiarare un'interfaccia per ogni combinazione. RessetableSetterIterator, RessetableRemovableIterator, RemovableSettableIterator, ecc. Le combinazioni crescono esponenzialmente alle funzionalità extra che aggiungiamo all'interfaccia.
Quello che di solito cerchiamo di esprimere è qualcosa come "questa funzione richiede un parametro che è Iterable, Settable e Ressetable" o "questa funzione restituisce un valore che è Iterable e Resettable". Eppure linguaggi come C #, Java e C ++ non ci permettono di farlo senza un uso scomodo dei generici.
Esistono metodi recenti che rendono obsoleto questo tipo di design?