例如,当我们向 Pane 添加新按钮时,我们需要编写以下代码:
StackPane pane = new StackPane();
pane.getChildren().add(new Button("OK"));
为什么我们需要调用“getChildren()”?它甚至做什么?我们为什么不能说:
StackPane pane = new StackPane();
pane.add(new Button("OK"));
我们将按钮添加到 Pane 中。我们不会将其添加到其子项中,
最佳答案
简短的答案就是“您必须那样做,因为这就是API的编写方式”。当然,您可能真正要问的是为什么要这样编写API。我认为这实际上是两个(相关的)问题。一种与方法名称有关,以及该方法的作用,另一种与实际的API设计有关。
通常,在计算中,重用现有的问题解决方案通常是有益的。 (知道何时进行此操作是一种艺术,但是在这两种情况下显然都是有益的。)通过两种不同的方式,此API设计将现有的解决方案重用到了众所周知的问题上,从而使之成为现实。对于以前遇到过这些解决方案的程序员来说,更容易理解和使用。
场景图
从总体上看,考虑一下通用用户界面的整体结构。有一个大容器(包含JavaFX场景的根源),其中包含各种UI组件。其中一些可能是简单的控件,例如按钮和标签等,其中一些可能是其他容器,这些容器又包含各种UI组件。当然,其中一些也可能是容器,依此类推。如果不强加某种结构,这可能变得复杂且难以使用。
为了理解结构,我们将其抽象化。有一个根节点,它有零个或多个其他节点的集合。每个节点都有零个或多个其他节点的集合,依此类推。这是计算中众所周知的抽象结构,称为“树”,基本上每个计算机程序员(无论使用哪种语言)都熟悉该结构。因此,如果我们将其视为树结构,因为我们已经很熟悉它,那么可以使复杂性更易于使用。为了将其视为树结构,我们对树状方面使用标准名称。每个节点(根节点除外)都只有一个“父”(它所属的容器):因此您会在Node
类中找到一个方法( getParent()
,这是树结构中的另一种术语),可以访问父节点(包含当前节点的容器)。类似地,包含其他节点的UI元素(节点)具有一种称为getChildren()
的方法,该方法返回当前节点中包含的所有节点的集合。 Oracle JavaFX教程在Scene graph上有一节对此进行了描述,其中包含许多漂亮的图表和相关代码。
简而言之,之所以有一种名为getChildren()
的方法,是因为我们将UI中所有事物的集合视为树结构,并且此方法名称准确地描述了该方法在所有集合的上下文中的作用UI元素是一棵树。具有一点经验的程序员可以立即识别术语“节点”,“父”和“子”,并帮助他们理解整体结构并与之合作。他们基本上可以立即推断出getChildren()
返回该容器中立即包含的所有UI元素的列表(该容器在您的示例中为StackPane
)。Pane
的API设计(例如StackPane
)
要考虑API设计,请考虑操作StackPane
中包含的内容,您可能想要做的所有事情。如您所见,您可能想向StackPane
添加一个新的UI元素(“节点”),因此您可能想要一个称为add(...)
的方法来接受Node
。但是,您可能还需要或想要做很多其他事情:
StackPane
删除一个节点StackPane
StackPane
StackPane
因此
StackPane
类的设计人员可能已经编写了所有这些方法,但是有更好的方法。如果考虑实现
StackPane
,则需要某种方式来跟踪其包含的节点(UI元素)。这些节点的顺序很重要,因此我们必须对其进行跟踪,并且需要有一种很好的方法来定义上面列出的所有功能。 StackPane
类(或其父类(super class)Pane
)的作者可能已经构建了一个数据结构来从头开始执行此操作,但是标准库中已经存在一个数据结构。拥有某种特定类型的对象的集合并跟踪其顺序的数据结构称为 List
1,并且List
接口(interface)是每个Java程序员都熟悉的接口(interface)。因此,如果您想到
StackPane
的实际实现,则可以在堆栈 Pane 本身中为上面列出的所有功能定义方法。最终看起来像什么(真的会比这更复杂,我只想在这里指出这一点)就像public class StackPane {
private final List<Node> children = new ArrayList<>(); // or some other list implementation...
public void add(Node node) {
children.add(node);
}
public boolean remove(Node node) {
return children.remove(node);
}
public void add(int index, Node node) {
children.add(index, node);
}
public boolean remove(int index) {
return children.remove(index);
}
public void addAll(Collection<Node> nodes) {
children.addAll(nodes);
}
// lots more methods like this...
// lots of layout code omitted...
}
我认为你说对了。所有这些代码实际上并没有做什么。只是调用已定义的行为。因此,可以使用列表本身的访问权限2,而不是这个this肿的API,而是可以向
StackPane
用户提供完全相同的功能:public class StackPane {
private final List<Node> children = new ArrayList<>();
public List<Node> getChildren() {
return children ;
}
// layout code omitted...
}
现在,该类的工作量大大减少了,API的工作量大大减少了,此外,用户还收到了
List
,正如前面所指出的,这是一种非常知名的对象类型。假设他们具有一定的Java经验,那么JavaFX的新程序员将已经熟悉List
的API,并且无需学习太多新知识就可以使用它。这样,程序员现在可以执行操作(使用非常规方式放置代码,并插入程序员的思想):StackPane pane = new StackPane();
Button button = new Button("OK");
pane.getChildren()
// oooh, a List, I know how those work
.add(button);
List<Label> lotsOfLabels = Arrays.asList(new Label("One"), new Label("Two"), new Label("Three"));
pane.getChildren()
// still a list, I know more things I can do here:
.addAll(lotsOfLabels);
pane.getChildren().remove(button);
pane.getChildren().clear();
我们有一个高兴的程序员,他立即了解API,并且在第一次接触JavaFX中的场景图时立即产生了生产力,因此,一个高兴的老板也看到了编程团队的高生产力。
因此,总而言之,通过简单地公开子节点的
List
,API可以公开处理StackPane
内容所需的所有功能,而无需在StackPane
类中添加大量方法并以利用每个Java程序员的现有知识。脚注
Pane
所需要的功能比List
中定义的要多:它们需要一种方便的方式来了解列表何时更改(通过添加或删除节点)(因为发生这种情况时 Pane 需要重新计算其布局)。因此,JavaFX团队定义了List
的子接口(interface)ObservableList
,它具有List
的所有功能,并添加了“观察”列表的功能。 List
定义的所有功能是否适合于子节点的集合?如果List
接口(interface)定义了一些在这里实际上没有意义的方法,那么使用此实现可能是一个坏主意,并且在第一个代码块中建议使用的更为膨胀的API实际上可能是一个更好的选择。 关于java - 在javaFx中,为什么在向窗口添加元素时为什么需要调用 “getChildren()”?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/41991574/