Ignora "pattern" e "anti-pattern" per ora. Implementa tutto ciò che ti serve in base alle tue esigenze.
In particolare, se hai bisogno di istanziare un'espressione di filtro booleano che agisce su StudentDetails
a runtime (cioè la composizione delle regole non è hard-coded) e poi usare in qualche modo i record filtrati, allora il tuo l'approccio è corretto e non dovresti preoccuparti di "anti-schemi" per ora.
Come promemoria, questo approccio ha un costo elevato di manutenzione del codice, in termini di numero di classi necessarie. E questi non sono generici: sono progettati specificamente per filtrare StudentDetails
e non possono essere utilizzati per filtrare altri tipi di informazioni. Potresti aver bisogno di molte dozzine di classi per implementare questa funzione di espressione del filtro booleano istanziabile in runtime.
Prima di implementare, controlla se il framework (s) e la libreria (e) che stai usando fornisce qualcosa di simile. Non riutilizzare la struttura esistente è forse il peggior anti-modello di tutti. In particolare, ti hanno fatto leggere quanto segue:
Se hai un milione di record in SQL, dovresti fare tutto in SQL, per quanto possibile. Altrimenti, se qualcuno esegue una query, aspettandosi al massimo 10 record (o solo 1), un filtro implementato all'interno dell'applicazione finirà per dover scaricare il milione di record. Se questo è il caso, ci sono diversi servizi che ti aiutano a convertire la tua espressione booleana in istruzioni SQL.
Per iniziare, avrai qualcosa di simile a questo:
interface StudentBoolFilter
{
bool Check(StudentDetails student);
}
interface StudentProcessor
{
void Process(StudentDetails student);
}
Quindi avrai bisogno di molte classi di meccanica:
class StudentAndFilter : StudentBoolFilter
{
List<StudentBoolFilter> filters;
void Add(StudentBoolFilter childFilter);
bool Check(StudentDetails student)
{
if (filters.Count == 0)
{
throw new Exception("typically wrong for business software to encounter an AND node without any children.");
}
foreach (StudentBoolFilter childFilter in filters)
{
if (!childFilter.check(student)) return false;
}
return true;
}
}
Allo stesso modo hai bisogno di StudentOrFilter
(riduzione booleana N-ary), StudentNotFilter
(unario) e così via.
Dal lato dell'elaborazione, avrete bisogno di un processore preconfezionato (sink) e consentire al programmatore dell'applicazione di implementare ulteriori classi di processore (cioè consentire alle classi client di implementare StudentProcessor
).
Questo processore (sink) scrive il% co_de ricevuto in una lista.
class StudentListAppender : StudentProcessor
{
IList<StudentDetails> targetList;
StudentListAppender(List<StudentDetails> targetList)
{
if (targetList.IsReadOnly) throw new Exception("Cannot write to read-only list!");
this.targetList = targetList;
}
void Process(StudentDetails student)
{
targetList.Add(student);
}
}
Infine, hai bisogno di un "driver" (una classe che invocherà tutto ciò che hai implementato finora) e presumo che questo "driver" prenderà il suo input da una lista. Nota che questo processore non implementa un'interfaccia.
class StudentListProcessor
{
StudentListProcessor(IList<StudentDetails> students, StudentBoolFilter filter, StudentProcessor processor)
{
// Store the three; do nothing.
// This class only stores one filter, but because
// a filter can be composite, and a tree of filters
// can be constructed at runtime, this gives unlimited
// possibility.
}
void Run()
{
foreach (StudentDetails student in students)
{
if (!filter.Check(student))
{
continue;
}
processor.Process(student);
}
}
}
Ricorda che questo è solo un modo per implementare questa funzionalità. In particolare, a causa della presenza della classe StudentDetails
, questa implementazione appartiene allo stile "push" del filtraggio degli elenchi. (La StudentListProcessor
è la classe che "spinge" gli oggetti attraverso la struttura del filtro booleano e infine al sink / processore.)
C'è un'altra implementazione che usa lo stile "pull". Un'implementazione in stile pull funzionerà come StudentListProcessor
(un'interfaccia che ha IEnumerator
(ottiene l'elemento corrente), Current
e MoveNext
).
L'implementazione di questo stile di attrazione insieme al filtro è leggermente più coinvolgente.
In C #, ogni filtro deve interrogare il filtro precedente per un elemento nell'implementazione di Reset
. Se l'oggetto ricevuto non valuta true nel filtro corrente, il filtro corrente deve continuare a chiamare MoveNext
del filtro precedente e MoveNext
per ottenere l'elemento successivo.
In Java, c'è un problema più grande, perché chiamare Current
richiede anche le stesse operazioni di hasNext
in C # - non può rispondere alla query MoveNext
senza ricevere effettivamente un elemento che abbia soddisfatto tutte le condizioni booleane in fasi precedenti.