Nascondere l'implementazione è un principio base di OOP e una buona idea in tutti i paradigmi, ma è particolarmente importante per gli iteratori (o qualunque sia il loro nome in quella specifica lingua) in linguaggi che supportano l'iterazione pigra.
Il problema con l'esposizione del tipo concreto di iterables - o anche di interfacce come IList<T>
- non è negli oggetti che li espongono, ma nei metodi che li usano. Ad esempio, supponiamo che tu abbia una funzione per stampare un elenco di Foo
s:
void PrintFoos(IList<Foo> foos)
{
foreach (foo in foos)
{
Console.WriteLine(foo);
}
}
Puoi utilizzare questa funzione solo per stampare elenchi di foo, ma solo se implementano IList<Foo>
IList<Foo> foos = //.....
PrintFoos(foos);
Ma cosa succede se si desidera stampare ogni elemento di indice pari dell'elenco? Dovrai creare un nuovo elenco:
IList<Foo> everySecondFoo = new List<T>();
bool isIndexEven = true;
foreach (foo; foos)
{
if (isIndexEven)
{
everySecondFoo.Add(foo);
}
isIndexEven = !isIndexEven;
}
PrintFoos(everySecondFoo);
Questo è piuttosto lungo, ma con LINQ possiamo farlo con un solo liner, che in realtà è più leggibile:
PrintFoos(foos.Where((foo, i) => i % 2 == 0).ToList());
Ora, hai notato .ToList()
alla fine? Converte la query lazy in un elenco, quindi possiamo passarlo a PrintFoos
. Ciò richiede l'assegnazione di un secondo elenco e due passaggi sugli elementi (uno sul primo elenco per creare il secondo elenco e un altro sul secondo elenco per stamparlo). Inoltre, cosa succede se abbiamo questo:
void Print6Foos(IList<Foo> foos)
{
int counter = 0;
foreach (foo in foos)
{
Console.WriteLine(foo);
++ counter;
if (6 < counter)
{
return;
}
}
}
// ........
Print6Foos(foos.Where((foo, i) => i % 2 == 0).ToList());
Che cosa succede se foos
ha migliaia di voci? Dovremo esaminarli tutti e assegnare una lista enorme solo per stamparne 6!
Inserisci Enumeratori - La versione di C # di The Iterator Pattern. Invece di fare in modo che la nostra funzione accetti un elenco, accettiamo Enumerable
:
void Print6Foos(Enumerable<Foo> foos)
{
// everything else stays the same
}
// ........
Print6Foos(foos.Where((foo, i) => i % 2 == 0));
Ora Print6Foos
può scorrere lentamente i primi 6 elementi dell'elenco e non è necessario toccarne il resto.
Non esporre la rappresentazione interna è la chiave qui. Quando Print6Foos
ha accettato una lista, abbiamo dovuto dargli una lista - qualcosa che supporta l'accesso casuale - e quindi abbiamo dovuto allocare una lista, perché la firma non garantisce che la itererà solo su di essa. Nascondendo l'implementazione, possiamo facilmente creare un oggetto Enumerable
più efficiente che supporti solo ciò di cui la funzione ha effettivamente bisogno.