可配置系统的设计模式



我有一个有趣的设计问题,我将尝试用一个简单的问题来简化:

我希望设计一个系统,其输出将是基于某些输入和中间处理的学生对象。流程如下:我有一个教室列表作为一种输入类型。要生成输出,处理步骤如下:

  1. 按X岁以下的学生(假设是10岁)筛选每个教室
  2. 按以下等级顺序对过滤后的结果进行排序:身高、体重、臂长
  3. 退回前8名学生

另一个输入可以简单地是我已经拥有并希望作为结果的一部分包含的学生列表。例如:输入1:三个学生的列表,输入2:两个教室的列表,上面的处理步骤将运行。

设计这样一个系统的最佳方法是:

  • 输入类型{student list|classroom list}
  • 过滤器类型{age|height|etc}
  • 排序{any ordering of height,weight,arm length}
  • returnNum {how many students to return}

系统应该足够灵活,以适应更多的输入类型和更多的排序顺序条目{例如。按鞋码给学生排序。我可以使用什么数据结构来建模本节的每个部分(即。表示排序顺序标准的最佳方式是什么?)有什么设计模式可以满足这些需求吗?任何帮助与架构设计将非常感激!

嗯,你所建议的可以很容易地用Java 8流完成,所以我猜你可以遵循的一种模式是管道。你也可以使用内部迭代器来实现:

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());

从需求来看,虽然StudentClassroom都是StudentSource s,但过滤和排序始终作用于Student(在示例输入中,它们从未按教室过滤或排序)。过滤非常简单:

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

排序通常是:

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

以上两个都是Visitor设计模式的应用。就像@Edwin的简洁回答一样,你将访问者排成一条管道(配置阶段),然后访问他们(执行阶段)。请注意,访客模式有"原因",嗯,学生应该在他们的"Gang of 4"书中读到。

你没怎么说:

  • 输入如何表示到程序中(例如:作为需要解析的文本)
  • 同一个学生是否可能出现在多个教室,或者学生名单……这与您可能选择将Comparator传递给哪个Java集合有关;

所以手头的任务归结为:

  1. 读取过滤器、分类器、限制器的定义,为这些
  2. 创建访问者
  3. 读取数据(教室/学生),对于每个发现的学生,执行if passesAllFilters(student) okstudents.add(student);,其中okstudentsjava.util.TreeSetComparator的启动,当达到限制时停止。

你可能会狡辩说"输入定义过滤器"的步骤是一个"工厂方法",但这真的没有帮助。List<Filter<Student>> getFilters(String filterSpec)并没有真正让你在工厂方法有用的地方。解析过滤器并编写代码来引用Students的特定属性、应用表达式等等,这可能不是一项简单的任务。根据过滤器需要允许的表达式类型,您可能需要查看像ANTLR 4这样的编译器生成器。您可能需要使用反射

看看https://docs.oracle.com/javase/7/docs/api/java/util/SortedMap.html

您可以使用多个SortedMap(每个键一个),其中您将列表的每个元素与每个映射(例如:sortedMapAge)的对应键放在一起。(卡尔,18). . sortedMapHeight(卡尔,"1.75 ") ...).

因此,使用迭代器,您可以使用适当的SortedMap通过各种键访问列表成员。

如果你想在进一步的抽象层中存储所有映射,你可以将它们存储在hashmap中,并使用键标识符(sortedMapAge…key年龄,sortedMapHeight高度....)

这是相当棘手的,但映射提供了一个很好的方法来组织对象与keyset

我个人会这样做:

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

也就是说,我将使用泛型抽象输入类型、用于过滤器和订单的命令模式,以及用于返回结果数量的纯int。

最新更新