【问题标题】:How to refactor this code using "Task" in Java?如何在 Java 中使用“任务”重构此代码?
【发布时间】:2021-12-30 15:08:31
【问题描述】:

问题是关于this code(在Android应用程序中):

private Task<QuerySnapshot> getVotesFromDB() {
    res = new int[]{0, 0, 0};   // a private class-member
    return FirebaseFirestore.getInstance().collection("Votes")
            .whereEqualTo("proposition_key", curr_proposition.getKey())
            .get().addOnCompleteListener(task -> {
                if (task.isSuccessful()) {
                    for (QueryDocumentSnapshot document : task.getResult()) {
                        String userChoice = (String) document.get("user_choice");
                        int choice;
                        switch (userChoice) {
                            case "against":
                                choice = 0;
                                break;
                            case "impossible":
                                choice = 1;
                                break;
                            case "agreement":
                                choice = 2;
                                break;
                            default:
                                throw new IllegalStateException("Unexpected value: " + userChoice);
                        }
                        res[choice]++;
                    }
                }
            });
}

一般来说,代码会从 Firestore 集合中读取一些行,并对它们应用一些“业务逻辑”(计算每种类型的字符串数)。未来,业务逻辑可能会变得比单纯的计数要复杂得多。所以我正在寻找一种方法来重构代码,以便可以将业务逻辑与数据库分开编写和测试。我想要的是具有某种形式的功能:

int[] countVotes(Generator<String> strings) {
     res = new int[3];
     (for String userChoice: strings)  {
          // Update res as above
     }
     return res;
}

可以在不需要数据库连接的情况下进行单元测试。那么,上面的函数可以重构如下:

private Generator<String> getVotesFromDB() {
    return FirebaseFirestore.getInstance().collection("Votes")
            .whereEqualTo("proposition_key", curr_proposition.getKey())
            .get().addOnCompleteListener(task -> {
                if (task.isSuccessful()) {
                    for (QueryDocumentSnapshot document : task.getResult()) {
                        userChoice = (String) document.get("user_choice");
                        yield userChoice;
                    }
                }
            });
}

然后运行类似的东西:

countVotes(getVotesFromDB())

问题是,我不知道如何使用异步函数调用来做到这一点。有没有办法以类似或更好的方式重构代码?

【问题讨论】:

  • Firebase 不为该套件中的任何产品提供同步 API。一切都是异步的,您需要应用适当的异步编程技术才能有效地使用它。您当然可以尝试阻止执行查询的线程,但这在 Android 上是一个非常糟糕的主意,因为这会导致应用程序冻结并可能因 ANR 而崩溃。
  • @DougStevenson 我的目标不一定是让代码同步——我的目标是将逻辑与数据库连接分离,这样逻辑可以单独进行单元测试,与不同的数据库一起使用,等等。还有其他方法吗?
  • 你不能直接从onCompleteListener 内部调用countVotes 吗?然后你仍然可以单独对countVotes 进行单元测试。
  • @TylerV 如何在使countVotes 接受字符串集合(或流)的同时做到这一点?
  • 为什么需要?难道它不能只取你从数据库中得到的字符串参数并更新 res 类成员(返回 void)吗?要对其进行测试,您只需多次调用它来模拟实际用途。

标签: java android firebase asynchronous google-cloud-firestore


【解决方案1】:

您可以将结果收集到一个数组中,然后使用单独的方法对其进行处理。然后,如果处理方法很复杂,则可以针对各种不同的结果列表单独进行单元测试。

private void getVotesFromDB() {
    FirebaseFirestore.getInstance().collection("Votes")
        .whereEqualTo("proposition_key", curr_proposition.getKey())
        .get().addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                ArrayList<String> results = new ArrayList();
                for (QueryDocumentSnapshot document : task.getResult()) {
                    String userChoice = (String) document.get("user_choice");
                    results.add(userChoice);
                }

                // assign the result to a class member
                res = countVotes(results);

                // do something to signal to the rest of the code
                // that results have been processed (e.g. post to
                // LiveData or call another "showResults" method)
            }
        });
}

然后,任何复杂的计数逻辑都可以独立于 firebase 调用。

int[] countVotes(ArrayList<String> choices) {
    int[] res = new int[]{0,0,0}; 

    // count the votes

    return res;
}

【讨论】:

  • 有没有办法让“countVotes”返回一个结果(一个 int[]),而不是更新一个成员变量?这将使测试更容易。
  • 这取决于你想在最后的情况下如何使用它,我可以添加一个选项来显示它如何与你发布到问题的版本一起工作。您不能从 onCompleteListener 中返回值,但如果您将结果设置为那里的类成员,您可以使 countVotes 调用只返回该结果
  • 虽然此解决方案有效,但如果数据库中的行数很大,则可能效率低下,因为它必须将许多字符串放入内存中的数组中。是否有使用流或生成器等不浪费内存的解决方案?
  • 您已经在内存中保存了一个完整文档及其数据的数组...如果您切换到使用 Kotlin,还有其他关于流和协程的选项。或者您可以调整数据处理以一次读取一个结果并跳过数组
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多