例如,当我们向窗格添加新按钮时,我们需要编写以下代码:
StackPane pane = new StackPane();
pane.getChildren().add(new Button("OK"));
为什么我们需要拨打" getChildren()?"它甚至做了什么?为什么我们不能说:
StackPane pane = new StackPane();
pane.add(new Button("OK"));
我们将按钮添加到窗格。我们不会将其添加到其子女身上,
答案 0 :(得分:5)
简短的回答只是"你必须这样做,因为这是API的编写方式"。当然,您可能真正要问的是为什么API就是这样编写的。我认为这真的相当于两个(相关的)问题。一个是关于方法名称,以及该方法的作用,一个是关于实际的API设计。
通常,在计算中,将现有解决方案重用于问题通常是有益的。 (知道何时执行此操作是有益的,但在这两种情况下,它显然都是一种好处。)这种API设计以两种不同的方式重用现有解决方案来解决众所周知的问题,因此,对于之前遇到过这些解决方案的程序员来说,更容易理解和使用。
总体而言,考虑一般用户界面的整体结构。有一个大容器(想想JavaFX中的场景的根),它包含各种UI组件。其中一些可能是简单的控件,如按钮和标签等,其中一些可能是其他容器,而其他容器又包含各种UI组件。当然,其中一些也可能是容器,等等。如果不强加某种结构,这可能会变得复杂且难以使用。
要理解结构,我们将其抽象化。有一个根节点,其中包含零个或多个其他节点的集合。其中每个都有零个或多个其他节点的集合,依此类推。这是一个众所周知的计算抽象结构,称为“树”,基本上每个计算机程序员(无论他们编程的是哪种语言)都熟悉它。因此,如果我们将其视为树结构,因为我们已经熟悉它,我们可以使复杂性更容易使用。为了将其视为树结构,我们对树状方面使用标准名称。每个节点(根节点除外)只有一个" parent" (它所属的容器):所以你在Node
类中找到一个方法("节点"是树结构的另一个术语),称为getParent()
,它允许访问父节点(包含当前节点的容器)。类似地,包含其他节点的UI元素(节点)具有名为getChildren()
的方法,该方法返回当前节点中包含的所有节点的集合。 Oracle JavaFX教程有一个关于Scene graph的部分描述了这一部分,其中包含许多漂亮的图表和相关代码。
因此,简而言之,有一个名为getChildren()
的方法的原因是因为我们将UI中所有内容的集合视为树结构,并且此方法名称准确描述了该方法的作用所有UI元素集合的上下文是树。术语"节点","父"和孩子"具有一点经验的程序员可以立即识别,并帮助他们理解整体结构并使用它。他们基本上可以立即推断getChildren()
返回该容器中包含的所有UI元素的列表(容器是示例中的StackPane
)。
Pane
s的API设计(例如StackPane
)要考虑API设计,请考虑在操纵StackPane
中包含的内容时可能要做的所有事情。如您所见,您可能希望向StackPane
添加新的UI元素("节点"),因此您可能需要一个名为add(...)
的方法接受Node
}。但是,您可能还需要或想要做其他一些事情:
StackPane
StackPane
StackPane
StackPane
所以StackPane
类的设计师可能刚刚编写了所有这些方法,但是有更好的方法。
如果您考虑实施StackPane
,您需要一些方法来跟踪它包含的节点(UI元素)。这些节点的顺序很重要,因此我们必须跟踪它,我们需要有一个很好的方法来定义上面列出的所有功能。 StackPane
类(或其超类,Pane
)的作者可以构建一个从头开始执行此操作的数据结构,但标准库中已存在一个。保存某些特定类型的对象集合并跟踪其顺序的数据结构称为List
1 ,List
接口是< em>每个Java程序员都熟悉。
因此,如果您考虑StackPane
的实际实现,您可以为堆栈窗格本身中列出的所有功能定义方法。这最终会找到一些东西(实际上它会比这更复杂,我只想在这里提出足够的意见),如
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 ,就可以为StackPane
用户提供完全相同的功能,而不是这个臃肿的API:
public class StackPane {
private final List<Node> children = new ArrayList<>();
public List<Node> getChildren() {
return children ;
}
// layout code omitted...
}
现在,类不那么臃肿,API不那么臃肿,另外,用户收到List
,如前所述,这是一种非常着名的对象类型。 JavaFX的新程序员,假设他们有一些(任何)Java经验,他们已经熟悉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
class 和以利用每个Java程序员的现有知识的方式完成。
<强>脚注强>
Pane
需要比List
中定义的更多功能:他们需要一种方便的方法来了解列表更改的时间,添加或删除节点(因为窗格需要发生这种情况时重新计算其布局)。因此,JavaFX团队定义了List
的子接口,称为ObservableList
,它具有List
的所有功能,并为&#34;观察&#34;添加了功能。清单。List
定义的适合子节点集合的功能吗?如果List
接口定义了一些在这里没有意义的方法,那么使用这个实现可能是一个坏主意,并且在第一个代码块中建议的更臃肿的API可能实际上更好选择。答案 1 :(得分:3)
而不是为add()
,addAll()
,remove()
,removeAll()
等儿童提供针对不同操作的单独方法。
很容易提供众所周知的集合,以便List<E>
支持的每个操作都可用,这将使该类变得简单且不那么冗长。在这种情况下,javafx.scene.layout.Pane
类会将子项存储在ObservableList<Node>
List<E>
答案 2 :(得分:0)
窗格会跟踪其子项的数组。您不能只调用word
的原因是因为StackPane类不提供该方法。您必须将元素直接添加到窗格的子数组中。