在javaFx中,为什么在向窗口添加元素时需要调用"getChildren()"?

Ron*_*gee 4 java user-interface javafx

例如,当我们向窗格添加新按钮时,我们需要编写以下代码:

 StackPane pane = new StackPane();
 pane.getChildren().add(new Button("OK"));
Run Code Online (Sandbox Code Playgroud)

为什么我们需要调用"getChildren()?" 它甚至做了什么?为什么我们不能说:

 StackPane pane = new StackPane();
 pane.add(new Button("OK"));
Run Code Online (Sandbox Code Playgroud)

我们将按钮添加到窗格中.我们不是把它添加到它的孩子,

Jam*_*s_D 5

简短的回答就是"你必须这样做,因为这就是API的编写方式".当然,您可能真正要问的是为什么API就是这样编写的.我认为这真的相当于两个(相关的)问题.一个是关于方法名称,以及该方法的作用,一个是关于实际的API设计.

通常,在计算中,将现有解决方案重用于问题通常是有益的.(知道何时执行此操作是有益的,但在这两种情况下,这显然都是一种好处.)这种API设计以两种不同的方式重用现有解决方案来解决众所周知的问题,从而使其成为现实的解决方案.对于之前遇到过这些解决方案的程序员来说,更容易理解和使用.


场景图

从整体上看,考虑一般用户界面的整体结构.有一个大容器(想想JavaFX中的场景的根),它包含各种UI组件.其中一些可能是简单的控件,如按钮和标签等,其中一些可能是其他容器,而其他容器又包含各种UI组件.当然,其中一些也可能是容器,等等.在不强加某种结构的情况下,这可能变得复杂且难以使用.

为了理解结构,我们将其抽象化.有一个根节点,其中包含零个或多个其他节点的集合.其中每个都有零个或多个其他节点的集合,依此类推.这是一个众所周知的计算抽象结构,称为"树",基本上每个计算机程序员(无论他们编程的语言)都熟悉.因此,如果我们将其视为树结构,因为我们已经熟悉它,我们可以使复杂性更容易使用.为了将其视为树结构,我们对树状方面使用标准名称.每个节点(根节点除外)只有一个"父"(它所属的容器):所以你会在Node类中找到一个方法("节点"是树结构中的另一个术语)getParent(),它给出了访问父节点(包含当前节点的容器).类似地,包含其他节点的UI元素(节点)具有一个被调用的方法getChildren(),该方法返回当前节点中包含的所有节点的集合.Oracle JavaFX教程有一个关于Scene图的部分描述了这一部分,其中包含许多漂亮的图表和相关代码.

因此,简而言之,调用方法的原因getChildren()是因为我们将UI中所有内容的集合视为树结构,并且此方法名称准确描述了该方法在所有集合的上下文中所执行的操作. UI元素是一棵树.术语"节点","父"和"子"对于具有一点经验的程序员来说是即刻可识别的,并帮助他们理解整体结构并使用它.它们基本上可以立即推断getChildren()返回包含在该容器中的所有UI元素的列表(容器StackPane在您的示例中).


API的设计Pane(如StackPane)

要考虑API设计,请考虑在操作包含的内容方面可能要做的所有事情StackPane.如您所见,您可能希望向其添加新的UI元素("节点")StackPane,因此您可能需要一个名为add(...)accept的方法Node.但是,您可能还需要或想要做其他一些事情:

  • 从中删除节点 StackPane
  • 将整个节点集合添加到 StackPane
  • 从中删除整个节点集合 StackPane
  • 从(清除)中删除所有节点 StackPane
  • 由于堆栈窗格中节点的顺序很重要(它定义了z顺序,即如果节点重叠,它定义了在前面和后面绘制的节点),您可能希望在特定位置插入节点(在前面)的东西,但在其他东西背后)
  • 移除"后面",或前面或某个指定位置的节点
  • 以及许多其他人

因此,StackPane该类的设计者可能只是编写了所有这些方法,但有一种更好的方法.

如果您考虑实现StackPane,您需要一些方法来跟踪它包含的节点(UI元素).这些节点的顺序很重要,因此我们必须跟踪它,我们需要有一个很好的方法来定义上面列出的所有功能.该报告的作者StackPane类(或它的超Pane)可能已经建立了一个数据结构,从头做到这一点,但一个已经在标准库的存在.保存某些特定类型的对象集合并跟踪其顺序的数据结构称为1,并且该接口是每个Java程序员熟悉的接口.ListList

因此,如果您考虑实际的实现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...
}
Run Code Online (Sandbox Code Playgroud)

我认为你说对了.所有这些代码都没有真正做任何事情; 它只是调用已经定义的行为.因此,StackPane通过提供对列表本身2的访问,可以为用户提供完全相同的功能,而不是这个臃肿的API :

public class StackPane {
    private final List<Node> children = new ArrayList<>();

    public List<Node> getChildren() {
        return children ;
    }

    // layout code omitted...
}
Run Code Online (Sandbox Code Playgroud)

现在,类不那么臃肿,API不那么臃肿,另外,用户收到一个List,如前所述,这是一种非常着名的对象类型.JavaFX的新程序员,假设他们有一些(任何)Java经验,他们已经熟悉了API,List并且不需要学习很多新东西才能使用它.所以程序员现在可以做到(代码布局非常规并且插入了程序员的想法):

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();
Run Code Online (Sandbox Code Playgroud)

我们有一个快乐的程序员,他们立刻就知道了API,并且在第一次遇到JavaFX中的场景图时遇到了高效的工作,因此是一个从编程团队中看到出色工作效率的快乐老板.

总而言之,通过简单地公开List子节点,API可以公开它操作内容所需的所有功能StackPane,而无需向StackPane类中添加大量方法,以利用现有知识的方式执行每个Java程序员.


脚注

  1. 实际上,Panes需要比定义更多的功能List:它们需要一种方便的方法来了解列表何时更改,添加或删除节点(因为窗格需要在发生这种情况时重新计算其布局).因此,JavaFX团队定义了一个List名为的子接口ObservableList,它具有List"观察"列表的所有功能并添加了功能.
  2. JavaFX团队有一件事需要在这里仔细思考:是否所有的功能List适用于子节点的集合?如果List接口定义了一些在这里没有意义的方法,那么使用这个实现可能是个坏主意,而第一个代码块中建议的更臃肿的API实际上可能是更好的选择.