简体   繁体   中英

Design pattern for configurable system

I have an interesting design problem that I will attempt to simplify in a toy problem below:

I wish to design a system for which the output will be student objects based on certain inputs and intermediary processing. The flow will be as follows: I have a list of classrooms as one type of input. To generate the output, the processing steps are:

  1. Filter each classroom by students under the age of X (lets say 10)
  2. Sort the filtered results by any permutation of this hierarchical order: height, weight, arm length
  3. Return the top 8 students.

Another input can simply be a list of students I already have and want included as part of the result. For example: Input 1: List of 3 students, Input 2: List of 2 classrooms for which the processing steps above will run.

What would be the best way to design such a system with inputs being:

  • input type {student list|classroom list} ,
  • filter type {age|height|etc} ,
  • sort order {any ordering of height,weight,arm length} ,
  • returnNum {how many students to return}

The system should be flexible enough to accommodate more input types and more sort order entries {ie. sort students by shoe size}. What data structure can I use to model each part of this section (ie. what is the best way to represent the sort order criteria?) Is there any design pattern that would fit these needs? Any help with the architecture design would be greatly appreciated!

Well, what you're suggesting can be done easily with Java 8 streams, so I guess one pattern you could follow is that of a pipeline . You could also implement this using internal iterators :

List<Student> found = Stream.of(student1, student2, student3, ..., studentn)
    .filter(s -> s.getAge() > 100)
    .sorted(Comparator.comparing(Student::getHeight).thenComparing(Student::getWeight))
    .limit(10)
    .collect(Collectors.toList());

From the requirements, although both Student and Classroom are StudentSource s, the filtering and sorting always act on Student (they're never filtered or sorted by classroom, in your sample inputs). Filtering is pretty easy:

// interface with a single method, reduces to a lambda too
interface Filter<T> {
    boolean accept(T candidate);
}

Sorting is canonically:

package java.util;
// interface with a single method, reduces to a lambda too
interface Comparable<T> {
    int compareTo(T a, T b);
}

Both of the above are applications of the Visitor design pattern. As with the succinct answer by @Edwin, you line up your visitors in a pipeline (configuration phase) and then visit them (execution phase). Notice that the Visitor pattern has 'reasons' which um, students should read up on in their 'Gang of 4' book.

You don't say much about:

  • how the inputs are represented to the program (eg. as text which needs to be parsed)
  • whether the same student might conceivably appear in more than 1 classroom, or the list-of-students ... this has bearing on which Java collection you might choose to pass the Comparator to;

So the task at hand boils down to:

  1. read the definitions of the filters, sorters, limiters, create visitors for these
  2. read the data (classrooms/students), and for each student discovered, do if passesAllFilters(student) okstudents.add(student); where okstudents is a java.util.TreeSet primed with a Comparator , stop when limit is reached.

You could possibly quibble that the step which takes 'input that defines filter(s)' is a 'factory method', but that's really of no help .. List<Filter<Student>> getFilters(String filterSpec) doesn't really get you anywhere where a factory method is useful . Parsing the filters and coming up with code which references particular properties of Students, applies the expressions, etc, may not be a trivial task. Depending on the types of expressions your filters are required to permit, you might want to look into a compiler-generator like ANTLR 4. You'll likely need to use reflection.

take a look at https://docs.oracle.com/javase/7/docs/api/java/util/SortedMap.html

you can use multiple SortedMap (one for every key), in which you put every element of your list whit the corresponding key for every map (es: sortedMapAge.put(carl, 18) ..sortedMapHeight(carl,"1.75") ...).

so with iterator you can access your list members through the varius keys using the appropriate SortedMap.

And if you want to store alla that maps in a further abstraction layer you can store them in a hashmap and use as key the key identifier (sortedMapAge...key Age, sortedMapHeight height ....)

it's quite tricky but maps offer you a good ways to organize objects with keyset.

Personally, I'd do it like this:

public <E> List<E> query(List<E> dataset, Filter<E> filter, Comparator<E> sortOrder, int maxResults);

That is, I'd use generics to abstract over the input type, the command pattern for the filters and orders, and a plain int for the number of results to return.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM