如何向java中的方法注入函数以减少代码重复?
How to inject function to method in java to reduce code repetition?
我目前正在学习 Java,在我当前的项目中,我需要根据不同的课程打印学生的统计数据:“Java”、“DSA”、“数据库”、“ Spring”。我设法获得了正确的数据,但正如您在下面的代码中看到的那样,它有很多代码重复。你知道如何注射吗? getJavaPoints()
当“Java”字符串传递给 printInfoAboutTopLearners(String courseName)
方法时的函数?
我目前不使用数据库作为存储库,因为它只是学习项目,存储库是 ArrayList<Student>。
目标是简化以下代码:
public static void printInfoAboutTopLearners(String courseName) {
System.out.println(courseName);
System.out.println("id points completed");
ArrayList<Student> students = StudentRepository.getStudentRepository();
if (courseName.equals("Java")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getJavaPoints).reversed())
.filter(student -> student.getJavaPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getJavaPoints(),
(double) student.getJavaPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("DSA")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getDsaPoints).reversed())
.filter(student -> student.getDsaPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getDsaPoints(),
(double) student.getDsaPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("Databases")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getDbPoints).reversed())
.filter(student -> student.getDbPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getDbPoints(),
(double) student.getDbPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("Spring")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getSpringPoints).reversed())
.filter(student -> student.getSpringPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getSpringPoints(),
(double) student.getSpringPoints() * 100 / JAVA_POINTS));
}
}
以下学生class:
public class Student {
private final int ID_BASE = 10000;
private int id;
private String firstName;
private String lastName;
private String email;
private int javaPoints;
private int dsaPoints;
private int dbPoints;
private int springPoints;
public Student(String firstName, String lastName, String email) {
this.id = idGenerator();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.javaPoints = 0;
this.dsaPoints = 0;
this.dbPoints = 0;
this.springPoints = 0;
}
public int idGenerator(){
return ID_BASE + StudentRepository.getSize();
} //Getters and Setters below
你在这里有很多事情要做,所以我建议你不要自己重写所有东西,而是接受 OOD 并稍微重新处理你的 classes。您已经有一个学生 class。您还应该有一个课程 class,其中包含学生及其分数的地图。然后,您可以从您的学生 class 中删除各种分数字段。我还会保留一个 CourseRepository class.
课程有学生,学生没有课程。
那么你的逻辑会是这样的:
- 使用课程名称从 CourseRepository 获取课程
- 获取获取课程的学生列表及其分数
- 对列表进行排序
- 输出列表
这就是我的建模方式:
public record Student(String firstName, String lastName, String studentID) {}
public record Course(String name, String id, Gradebook gradebook) {}
Student
和 Course
对象不应(或不需要)彼此直接相关。学生没有课程。一名学生注册了一门课程。同样,课程没有学生。课程的属性至少应限于其名称和 ID。课程可以具有其他属性,如部分 ID、可用性的布尔标志、容量(最大座位数)和描述。我使用 Java 记录来消除样板代码。此外,这些对象是不可变的(并且本质上是线程安全的),因为一旦创建它们就没有理由改变它们。我仍然可以修改成绩簿,您稍后会看到。
然后您需要创建 classes 来完成学生与课程之间的关联。我想到的是 Enrollment
。在此 class 中,您将获得一个课程列表,其中包含注册特定课程的学生列表。为此,我将使用 class.
public class Enrollment {
private Map<Course, List<Student>>
// rest of class omitted
}
这是注册办公室需要的实体。对于这个例子,我指出 Gradebook
是 Course
记录的一个属性。这意味着 Course
对象有一个 Gradebook
。成绩簿是学生及其特定成绩的记录。这是 class,对于您的示例,链接或关联学生和课程成绩。
public class Gradebook {
private Map<Student, List<Integer>> grades = new HashMap<>(); // mapping of student IDs and course grades
public List<Integer> getGrades(Student student) {
return grades.get(student);
}
public double getGPA(Student student) {
return getGrades(student).stream().mapToInt((x) -> x).summaryStatistics().getAverage();
}
public void addStudent(Student student) {
grades.put(student, new ArrayList<Integer>());
}
public void enterStudentGrade(Student student, int points) {
List<Integer> gradePoints = grades.get(student);
gradePoints.add(points);
grades.put(student, gradePoints);
}
public TopPerformer getTopPerformer() {
Map.Entry<Student, Double> topPerformer = grades.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
e-> e.getValue()
.stream()
.mapToInt(Integer::intValue)
.average()
.getAsDouble())).entrySet().stream().max((Entry<?, Double> e1, Entry<?, Double> e2) -> e1.getValue()
.compareTo(e2.getValue())).get();
String name = topPerformer.getKey().firstName() + " " + topPerformer.getKey().lastName();
return new TopPerformer(name, topPerformer.getValue());
}
public record TopPerformer(String name, double points) {}
}
值得注意的是,我没有为成绩簿使用 record
,因为我希望能够在最初创建成绩簿后更新(变异)成绩簿。
现在您已经有了这个框架,您可以创建一个函数来找出(计算)每门课程的最佳表现者。问题是:你把这个方法放在哪里? IMO,因为 Gradebook
实例包含学生及其特定成绩的记录,所以您可以让这个 class 计算平均值和 return 它的优等生。为简单起见,我将其添加到此 class。我也相信它属于这里,因为 Gradebook
class 的目的是管理学生成绩,计算最佳表现者似乎是此 class 的派生功能。我还向这个 class 添加了以下 main
方法来测试代码:
public static void main(String[] args) {
Student steve = new Student("Steve", "Jobs", "12345");
Student bill = new Student("Bill", "Gates", "67890");
Student john = new Student ("John", "Doe", "98765");
Student jane = new Student ("Jane", "Doe", "31245");
Course java = new Course("Java", "COMP1010", new Gradebook());
Course dsa = new Course("DSA", "COMP1020", new Gradebook());
dsa.gradebook().addStudent(john);
dsa.gradebook().addStudent(bill);
java.gradebook().addStudent(steve);
java.gradebook().addStudent(jane);
java.gradebook().enterStudentGrade(jane, 100);
java.gradebook().enterStudentGrade(jane, 95);
java.gradebook().enterStudentGrade(jane, 97);
java.gradebook().enterStudentGrade(steve, 96);
java.gradebook().enterStudentGrade(steve, 95);
java.gradebook().enterStudentGrade(steve, 97);
dsa.gradebook().enterStudentGrade(bill, 100);
dsa.gradebook().enterStudentGrade(bill, 95);
dsa.gradebook().enterStudentGrade(bill, 97);
dsa.gradebook().enterStudentGrade(john, 96);
dsa.gradebook().enterStudentGrade(john, 95);
dsa.gradebook().enterStudentGrade(john, 97);
System.out.println(java.gradebook().getTopPerformer());
System.out.println(dsa.gradebook().getTopPerformer());
}
没有分支来确定课程名称和计算最佳表现者。无论是什么课程(当前课程或今天不存在的课程),它都会使用相同的函数计算出表现最好的人;这是消除重复代码的问题的核心。
执行上面的代码输出如下:
TopPerformer[name=Jane Doe, points=97.33333333333333]
TopPerformer[name=Bill Gates, points=97.33333333333333]
TopPerformer
对象不需要是记录。它也不需要是 class。您可以简单地稍微修改代码和 return 优等生及其 GPA 的地图。
虽然还有其他改进领域,如 class 设计等,如其他答案中所建议的。下面的代码演示了向方法中注入一个函数以从方法中取出细节,同时只保留内部的通用逻辑。这种方法在函数式编程范式术语中通常被称为“高阶函数”。
public static void printInfo(String courseName) {
switch (courseName) {
case "Java":
printInfoAboutTopLearners(Student::getJavaPoints);
break;
case "DSA":
printInfoAboutTopLearners(Student::getDsaPoints);
break;
case "Databases":
printInfoAboutTopLearners(Student::getDbPoints);
break;
case "Spring":
printInfoAboutTopLearners(Student::getSpringPoints);
}
}
public static void printInfoAboutTopLearners(Function<Student, Integer> valueGetter) {
System.out.println("id points completed");
List<Student> students = StudentRepository.getStudentRepository();
students
.stream()
.sorted(Comparator.comparing(valueGetter).reversed())
.filter(student -> valueGetter.apply(student) != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
valueGetter.apply(student),
(double) valueGetter.apply(student) * 100 / JAVA_POINTS));
}
我目前正在学习 Java,在我当前的项目中,我需要根据不同的课程打印学生的统计数据:“Java”、“DSA”、“数据库”、“ Spring”。我设法获得了正确的数据,但正如您在下面的代码中看到的那样,它有很多代码重复。你知道如何注射吗? getJavaPoints()
当“Java”字符串传递给 printInfoAboutTopLearners(String courseName)
方法时的函数?
我目前不使用数据库作为存储库,因为它只是学习项目,存储库是 ArrayList<
目标是简化以下代码:
public static void printInfoAboutTopLearners(String courseName) {
System.out.println(courseName);
System.out.println("id points completed");
ArrayList<Student> students = StudentRepository.getStudentRepository();
if (courseName.equals("Java")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getJavaPoints).reversed())
.filter(student -> student.getJavaPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getJavaPoints(),
(double) student.getJavaPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("DSA")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getDsaPoints).reversed())
.filter(student -> student.getDsaPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getDsaPoints(),
(double) student.getDsaPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("Databases")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getDbPoints).reversed())
.filter(student -> student.getDbPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getDbPoints(),
(double) student.getDbPoints() * 100 / JAVA_POINTS));
}
if (courseName.equals("Spring")) {
students
.stream()
.sorted(Comparator.comparingInt(Student::getSpringPoints).reversed())
.filter(student -> student.getSpringPoints() != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
student.getSpringPoints(),
(double) student.getSpringPoints() * 100 / JAVA_POINTS));
}
}
以下学生class:
public class Student {
private final int ID_BASE = 10000;
private int id;
private String firstName;
private String lastName;
private String email;
private int javaPoints;
private int dsaPoints;
private int dbPoints;
private int springPoints;
public Student(String firstName, String lastName, String email) {
this.id = idGenerator();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.javaPoints = 0;
this.dsaPoints = 0;
this.dbPoints = 0;
this.springPoints = 0;
}
public int idGenerator(){
return ID_BASE + StudentRepository.getSize();
} //Getters and Setters below
你在这里有很多事情要做,所以我建议你不要自己重写所有东西,而是接受 OOD 并稍微重新处理你的 classes。您已经有一个学生 class。您还应该有一个课程 class,其中包含学生及其分数的地图。然后,您可以从您的学生 class 中删除各种分数字段。我还会保留一个 CourseRepository class.
课程有学生,学生没有课程。
那么你的逻辑会是这样的:
- 使用课程名称从 CourseRepository 获取课程
- 获取获取课程的学生列表及其分数
- 对列表进行排序
- 输出列表
这就是我的建模方式:
public record Student(String firstName, String lastName, String studentID) {}
public record Course(String name, String id, Gradebook gradebook) {}
Student
和 Course
对象不应(或不需要)彼此直接相关。学生没有课程。一名学生注册了一门课程。同样,课程没有学生。课程的属性至少应限于其名称和 ID。课程可以具有其他属性,如部分 ID、可用性的布尔标志、容量(最大座位数)和描述。我使用 Java 记录来消除样板代码。此外,这些对象是不可变的(并且本质上是线程安全的),因为一旦创建它们就没有理由改变它们。我仍然可以修改成绩簿,您稍后会看到。
然后您需要创建 classes 来完成学生与课程之间的关联。我想到的是 Enrollment
。在此 class 中,您将获得一个课程列表,其中包含注册特定课程的学生列表。为此,我将使用 class.
public class Enrollment {
private Map<Course, List<Student>>
// rest of class omitted
}
这是注册办公室需要的实体。对于这个例子,我指出 Gradebook
是 Course
记录的一个属性。这意味着 Course
对象有一个 Gradebook
。成绩簿是学生及其特定成绩的记录。这是 class,对于您的示例,链接或关联学生和课程成绩。
public class Gradebook {
private Map<Student, List<Integer>> grades = new HashMap<>(); // mapping of student IDs and course grades
public List<Integer> getGrades(Student student) {
return grades.get(student);
}
public double getGPA(Student student) {
return getGrades(student).stream().mapToInt((x) -> x).summaryStatistics().getAverage();
}
public void addStudent(Student student) {
grades.put(student, new ArrayList<Integer>());
}
public void enterStudentGrade(Student student, int points) {
List<Integer> gradePoints = grades.get(student);
gradePoints.add(points);
grades.put(student, gradePoints);
}
public TopPerformer getTopPerformer() {
Map.Entry<Student, Double> topPerformer = grades.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
e-> e.getValue()
.stream()
.mapToInt(Integer::intValue)
.average()
.getAsDouble())).entrySet().stream().max((Entry<?, Double> e1, Entry<?, Double> e2) -> e1.getValue()
.compareTo(e2.getValue())).get();
String name = topPerformer.getKey().firstName() + " " + topPerformer.getKey().lastName();
return new TopPerformer(name, topPerformer.getValue());
}
public record TopPerformer(String name, double points) {}
}
值得注意的是,我没有为成绩簿使用 record
,因为我希望能够在最初创建成绩簿后更新(变异)成绩簿。
现在您已经有了这个框架,您可以创建一个函数来找出(计算)每门课程的最佳表现者。问题是:你把这个方法放在哪里? IMO,因为 Gradebook
实例包含学生及其特定成绩的记录,所以您可以让这个 class 计算平均值和 return 它的优等生。为简单起见,我将其添加到此 class。我也相信它属于这里,因为 Gradebook
class 的目的是管理学生成绩,计算最佳表现者似乎是此 class 的派生功能。我还向这个 class 添加了以下 main
方法来测试代码:
public static void main(String[] args) {
Student steve = new Student("Steve", "Jobs", "12345");
Student bill = new Student("Bill", "Gates", "67890");
Student john = new Student ("John", "Doe", "98765");
Student jane = new Student ("Jane", "Doe", "31245");
Course java = new Course("Java", "COMP1010", new Gradebook());
Course dsa = new Course("DSA", "COMP1020", new Gradebook());
dsa.gradebook().addStudent(john);
dsa.gradebook().addStudent(bill);
java.gradebook().addStudent(steve);
java.gradebook().addStudent(jane);
java.gradebook().enterStudentGrade(jane, 100);
java.gradebook().enterStudentGrade(jane, 95);
java.gradebook().enterStudentGrade(jane, 97);
java.gradebook().enterStudentGrade(steve, 96);
java.gradebook().enterStudentGrade(steve, 95);
java.gradebook().enterStudentGrade(steve, 97);
dsa.gradebook().enterStudentGrade(bill, 100);
dsa.gradebook().enterStudentGrade(bill, 95);
dsa.gradebook().enterStudentGrade(bill, 97);
dsa.gradebook().enterStudentGrade(john, 96);
dsa.gradebook().enterStudentGrade(john, 95);
dsa.gradebook().enterStudentGrade(john, 97);
System.out.println(java.gradebook().getTopPerformer());
System.out.println(dsa.gradebook().getTopPerformer());
}
没有分支来确定课程名称和计算最佳表现者。无论是什么课程(当前课程或今天不存在的课程),它都会使用相同的函数计算出表现最好的人;这是消除重复代码的问题的核心。
执行上面的代码输出如下:
TopPerformer[name=Jane Doe, points=97.33333333333333]
TopPerformer[name=Bill Gates, points=97.33333333333333]
TopPerformer
对象不需要是记录。它也不需要是 class。您可以简单地稍微修改代码和 return 优等生及其 GPA 的地图。
虽然还有其他改进领域,如 class 设计等,如其他答案中所建议的。下面的代码演示了向方法中注入一个函数以从方法中取出细节,同时只保留内部的通用逻辑。这种方法在函数式编程范式术语中通常被称为“高阶函数”。
public static void printInfo(String courseName) {
switch (courseName) {
case "Java":
printInfoAboutTopLearners(Student::getJavaPoints);
break;
case "DSA":
printInfoAboutTopLearners(Student::getDsaPoints);
break;
case "Databases":
printInfoAboutTopLearners(Student::getDbPoints);
break;
case "Spring":
printInfoAboutTopLearners(Student::getSpringPoints);
}
}
public static void printInfoAboutTopLearners(Function<Student, Integer> valueGetter) {
System.out.println("id points completed");
List<Student> students = StudentRepository.getStudentRepository();
students
.stream()
.sorted(Comparator.comparing(valueGetter).reversed())
.filter(student -> valueGetter.apply(student) != 0)
.forEach(student -> System.out.printf(Locale.US,
"%d %-8d %.1f%%\n",
student.getId(),
valueGetter.apply(student),
(double) valueGetter.apply(student) * 100 / JAVA_POINTS));
}