Please ignore "patterns" and "anti-patterns" for now. Implement whatever you need according to your requirements.
In particular, if you have a need to instantiate a boolean filter expression that acts on
StudentDetails at runtime (i.e. the composition of the rules aren't hard-coded) and then to use the filtered records somehow, then your approach is correct and you shouldn't worry about "anti-patterns" for now.
Just as a reminder, this approach has a high code maintenance cost, in terms of the number of classes you need. And these aren't generic - they are specifically designed for filtering
StudentDetails, and can't be used to filter other types of information. You may need many dozens of classes in order to implement this runtime-instantiable boolean filter expression feature.
Before implementing, check whether the framework(s) and library(s) you are using provides something similar. Not reusing existing facility is perhaps the worst anti-pattern of all. In particular, make you you have read the following:
If you have a million records in SQL, you should do everything in SQL, to the extent possible. Otherwise, if someone performs a query, expecting at most 10 records (or just 1), a filter that is implemented inside the application will end up having to download the one million records. If this is the case, there are several facilities that help you convert your boolean expression into SQL statements.
To begin with, you will have something similar to this:
bool Check(StudentDetails student);
void Process(StudentDetails student);
Then, you will need lots of mechanics classes:
class StudentAndFilter : StudentBoolFilter
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;
Similarly you need
StudentOrFilter (N-ary boolean reduction),
StudentNotFilter (unary), and so on.
On the processing side, you will need one prepackaged processor (sink), and allow the application programmer to implement additional processor classes (i.e. allow client classes to implement
This one processor (sink) writes the received
StudentDetails into a list.
class StudentListAppender : StudentProcessor
if (targetList.IsReadOnly) throw new Exception("Cannot write to read-only list!");
this.targetList = targetList;
void Process(StudentDetails student)
Finally, you need one "driver" (a class that will invoke everything you have implemented so far), and I assume that this "driver" will take its input from a list. Note that this processor doesn't implement an interface.
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
foreach (StudentDetails student in students)
Keep in mind this is just one way of implementing this functionality. In particular, because of the presence of the
StudentListProcessor class, this implementation belongs to the "push" style of list filtering. (The
StudentListProcessor is the class that "pushes" items through the boolean filter tree and finally to the sink/processor.)
There is another implementation that uses "pull" style. A pull-style implementation will act as
IEnumerator (an interface that has
Current (gets the current item),
Implementing in this pull-style alongside filtering is slightly more involved.
In C#, each filter has to query the the previous filter for an item in the implementation of
MoveNext. If the received item doesn't evaluate to true in the current filter, the current filter has to keep calling the previous filter's
Current to get the next item.
In Java, there is a bigger issue, because calling
hasNext also requires the same operations as the
MoveNext in C# - it cannot answer the
hasNext query without actually receiving an item that has satisfied all boolean conditions in previous stages.