在构造 JavaFX 控制器时,好的编码实践是什么?
我是一名学习如何使用 JavaFX 的学生,我通过使用 SceneBuilder 和 Controller 类获得了我的第一个 GUI。然而,从我的角度来看,控制器中的代码结构看起来非常混乱和丑陋,因为我将每个事件处理程序都放在了控制器的initialize()方法中。这使得它看起来像这样:
@FXML
private void initialize() {
dealtHandLabel.setText("Your cards will be shown here.");
TextInputDialog userInput = new TextInputDialog();
userInput.setTitle("How many cards?");
userInput.setHeaderText("Enter how many cards you want to get.");
userInput.setContentText("No. of cards:");
//This makes it so that the button displays a hand of cards (of specified amount) when clicked
dealHand.setOnAction(event -> {
Optional<String> result = userInput.showAndWait();
if(result.isPresent()) {
int requestedAmount = Integer.parseInt(result.get());
StringBuilder sb = new StringBuilder();
cardHand = deck.dealHand(requestedAmount);
cardHand.forEach((card) -> sb.append(card.getAsString()).append(" "));
dealtHandLabel.setText(sb.toString());
}
});
//This button uses lambdas and streams to display requested information (sum, heart cards, etc.)
checkHand.setOnAction(event -> {
int cardSum = cardHand.stream().mapToInt(card -> card.getFace()).sum();
List<PlayingCard> spadeCards = cardHand.stream().filter((card) -> card.getSuit() == 'S').toList();
List<PlayingCard> heartCards = cardHand.stream().filter((card) -> card.getSuit() == 'H').toList();
List<PlayingCard> diamondCards = cardHand.stream().filter((card) -> card.getSuit() == 'D').toList();
List<PlayingCard> clubCards = cardHand.stream().filter((card) -> card.getSuit() == 'C').toList();
StringBuilder sb = new StringBuilder();
heartCards.forEach((card) -> sb.append(card.getAsString()).append(" "));
sumOfFacesField.setText(String.valueOf(cardSum));
heartCardsField.setText(sb.toString());
if(heartCards.size() >= 5 || diamondCards.size() >= 5 || spadeCards.size() >= 5 || clubCards.size() >= 5) {
flushField.setText("Yes");
}
else {
flushField.setText("No");
}
if(cardHand.stream().anyMatch((card) -> card.getAsString().equals("S12"))) {
spadesQueenField.setText("Yes");
}
else {
spadesQueenField.setText("No");
}
});
}
我的讲师做了完全相同的事情,他直接将每个节点处理程序放入初始化方法中,但我不确定这是否是良好的编码实践,因为从我的角度来看,这使得代码更难阅读。将不同的处理程序放入单独的方法中并使用 SceneBuilder 将它们连接到正确的节点会更好吗?或者将所有内容放入初始化中是否被认为是 JavaFX 开发人员中常见的编码实践?
I am a student learning how to use JavaFX and I've got my first GUI working by using SceneBuilder and a Controller class. However, from my point of view the structure of the code in the controller looks incredibly messy and ugly because I put every event handler in the initialize() method of Controller. This makes it look like this:
@FXML
private void initialize() {
dealtHandLabel.setText("Your cards will be shown here.");
TextInputDialog userInput = new TextInputDialog();
userInput.setTitle("How many cards?");
userInput.setHeaderText("Enter how many cards you want to get.");
userInput.setContentText("No. of cards:");
//This makes it so that the button displays a hand of cards (of specified amount) when clicked
dealHand.setOnAction(event -> {
Optional<String> result = userInput.showAndWait();
if(result.isPresent()) {
int requestedAmount = Integer.parseInt(result.get());
StringBuilder sb = new StringBuilder();
cardHand = deck.dealHand(requestedAmount);
cardHand.forEach((card) -> sb.append(card.getAsString()).append(" "));
dealtHandLabel.setText(sb.toString());
}
});
//This button uses lambdas and streams to display requested information (sum, heart cards, etc.)
checkHand.setOnAction(event -> {
int cardSum = cardHand.stream().mapToInt(card -> card.getFace()).sum();
List<PlayingCard> spadeCards = cardHand.stream().filter((card) -> card.getSuit() == 'S').toList();
List<PlayingCard> heartCards = cardHand.stream().filter((card) -> card.getSuit() == 'H').toList();
List<PlayingCard> diamondCards = cardHand.stream().filter((card) -> card.getSuit() == 'D').toList();
List<PlayingCard> clubCards = cardHand.stream().filter((card) -> card.getSuit() == 'C').toList();
StringBuilder sb = new StringBuilder();
heartCards.forEach((card) -> sb.append(card.getAsString()).append(" "));
sumOfFacesField.setText(String.valueOf(cardSum));
heartCardsField.setText(sb.toString());
if(heartCards.size() >= 5 || diamondCards.size() >= 5 || spadeCards.size() >= 5 || clubCards.size() >= 5) {
flushField.setText("Yes");
}
else {
flushField.setText("No");
}
if(cardHand.stream().anyMatch((card) -> card.getAsString().equals("S12"))) {
spadesQueenField.setText("Yes");
}
else {
spadesQueenField.setText("No");
}
});
}
My lecturer does the exact same thing where he straight up puts every node handler into the initialize method, but I am not sure if this is good coding practice because it makes code harder to read from my point of view. Would it be better to put the different handlers into separate methods and connect them to the correct nodes using SceneBuilder, or is putting everything into initialize considered common coding practice among JavaFX developers?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
这是一个固执己见,甚至可能是武断的决定,两种方法都可以。
与从 FXML 引用事件方法处理程序相比,在初始化函数中编写事件处理程序没有任何问题。
这类事情通常超出了 StackOverflow 的范围,但无论如何我都会添加一些指示和意见,因为无论 StackOverflow 政策如何,它都可能对您或其他人有所帮助。
引用 Scene Builder 中的操作
就个人而言,我会引用 Scene Builder 中的操作:
onAction
或其他事件的值对于突出显示的节点。这还将在 FXML 中添加引用,因此您将得到类似的内容,其中包含
onAction
属性的主题标签值:让场景生成器生成骨架(
View | Show Sample Skeleton
代码>)。这将创建一个要填写的方法签名,如下所示:然后将事件处理代码放入其中。
对于该设置,Idea 等 IDE 将执行额外的智能检查以确保一致性,并允许 FXML 和 Java 代码之间的元素导航,这可能很好,但并不是真正关键。
以下是有关 JavaFX 控制器的一些重要设计决策的可选附加信息。
如果它让您感到困惑或与您的申请无关(这可能适用于小型研究申请),请忽略它。
考虑使用 MVC 和共享模型
对于控制器而言,更重要的设计决策通常是是否使用共享模型和 MVC、MVP 或 MVVM。
我鼓励您研究这一点,研究缩写词,并查看 Eden 编码 MVC 指南。
考虑使用依赖注入
还要考虑是否在控制器中使用依赖注入框架,例如 Spring 集成(对于更复杂的应用程序)或聪明的 eden 注入模式。您不需要使用这些模式,但它们可以提供帮助。 Spring 尤其复杂,并且与 JavaFX 的集成目前有点棘手。我两者都知道,所以如果应用程序需要的话我会使用它们,但对于其他人来说,这可能不是一个很好的组合。
考虑业务服务层
对于中型到大型应用程序,除了拥有单独的模型层之外,还应尝试将业务逻辑从控制器中分离出来,以便控制器仅处理业务服务上的调用功能操作共享模型并将该模型绑定到 UI,而不是直接在控制器中实现业务逻辑。
这使得重用、推理和测试业务逻辑变得更加容易。对于较小的应用程序,不需要额外的抽象,您可以在控制器中完成工作。
通常,这样的处理程序将调用与共享模型交互的注入服务。如果更新后的数据也需要持久化,那么注入的服务还可以调用数据库访问对象或Rest API客户端来持久化数据。
将它们放在一起
因此,回到前面的示例,您可以在控制器中实现保存功能,如下所示:
其中 userService 可能引用 Spring WebFlux REST 客户端以将新用户保存到云- 部署 REST 服务或 Spring Data DAO,将新用户存储在共享 RDBMS 数据库中。
如前所述,并非所有应用程序都需要这种抽象级别。特别是小型应用程序不需要的注入框架。您可以在给定的应用程序中混合架构风格,适当使用共享模型和服务,如果您喜欢以这种方式编码,则可以直接在控制器中编写一些较小的函数。如果您确实混合设计模式,请小心,以免最终变得一团糟:-)
This is an opinionated, perhaps even arbitrary decision, both approaches are OK.
There is nothing wrong with coding the event handlers in the initialize function versus referencing an event method handler from FXML.
These kinds of things are out of usually out of scope for StackOverflow, but I'll add some pointers and opinions anyway as it may help you or others regardless of StackOverflow policy.
Reference the actions in Scene Builder
Personally, I'd reference the action in Scene Builder:
onAction
or other events in the code panel section of Scene Builder UI for the highlighted node.This will also add the reference in FXML, so you will have something like this, with the hashtag value for the
onAction
attribute:Have Scene Builder generate a skeleton (
View | Show Sample Skeleton
). This will create a method signature to fill in, like this:Then place the event handling code in there.
For that setup, IDEs such as Idea will do additional intelligent checks for consistency and allow element navigation between FXML and Java code, which can be nice, though that isn't really critical.
What follows is optional additional information on some important design decisions regarding JavaFX controllers.
Ignore this if it confuses you or is not relevant for your application (which is likely for a small study application).
Consider using MVC and a shared model
The more important design decision with regards to controllers is, usually, whether or not to use a shared model and MVC, MVP, or MVVM.
I'd encourage you to study that, research the acronyms, and look at the Eden coding MVC guide.
Consider using dependency injection
Also consider whether or not to use a dependency injection framework with the controllers, e.g. Spring integration (for a more complex app) or the clever eden injection pattern. You don't need to use these patterns, but they can help. Spring in particular is complex, and the integration with JavaFX is currently a bit tricky. I know both, so I would use them if the app called for it, but for others, it may not be a good combination.
Consider a business services layer
For medium to larger sized apps, in addition to having a separate model layer, try to separate the business logic out of the controller so the controller is just dealing with invoking functions on business services that manipulate the shared model and binding that model to the UI, rather than directly implementing the business logic in the controller.
This makes reusing, reasoning about, and testing the business logic easier. For smaller apps, the additional abstraction is not necessary and you can do the work in the controller.
Often such a handler will call an injected service that interacts with the shared model. If the updated data also needs to be persisted, then the injected service can also invoke a database access object or rest API client to persist the data.
Putting it all together
So to go back to the prior example, you might implement your save function in the controller like this:
Where the userService might reference a Spring WebFlux REST Client to persist the new user to a cloud-deployed REST service or maybe a Spring Data DAO to store the new user in a shared RDBMS database.
As noted, not all apps need this level of abstraction. Especially, the injection frameworks that are not required for small apps. And you can mix architectural styles within a given app, using shared models and services as appropriate and writing some smaller functions directly in the controller if you prefer to code that way. Just be careful if you do mix design patterns that it doesn't end up a jumbled mess :-)