简体   繁体   English

根据Java中的对象类型调用子类的方法

[英]Call Subclasses' Method Depending on Object Type in Java

PROBLEM: Dynamically choose corresponding subclass and call its method, depending on passed parameter type. 问题:根据传递的参数类型,动态选择相应的子类并调用其方法。

RESEARCH: Struggled to apply the generics approach to the task: 研究:努力将泛型方法应用于任务:

public abstract class MetaContainer
  extends Node {

  public static abstract interface CommonContainer {
    ObservableList<Object> getChildren(Object container);
  }

  public abstract class AnchorPaneContainer
    extends AnchorPane
    implements CommonContainer {

    public ObservableList<Object> getChildren(Object container) {
      // Special approach for AnchorPanes.
    }
  }

  public abstract class TabPaneContainer
    extends TabPane
    implements CommonContainer {

    public ObservableList<Object> getChildren(Object container) {
      // Special approach for TabPanes.
    }
  }
}

I try to use the class like this (and get error, because CommonContainer is an interface and cannot have static methods): 我尝试使用这样的类(并且会出错,因为CommonContainer是接口并且不能具有静态方法):

  private ObservableList<Node> getElements(Node container)
    throws ClassNotFoundException {

    ObservableList<Node> nodes = FXCollections.observableArrayList();
    ObservableList<Object> objects = FXCollections.observableArrayList();

    objects.addAll(
      MetaContainer.CommonContainer.
      getChildren(
        (Object) container));

    for (Object object : objects) {
      nodes.add(
        (Node) object);
    }

    return nodes;
  }

QUESTION: How can I call getChildren() on the whole MetaContainer and pass it container of any type in parameter, waiting it to address the correct getChildren() in subclass of container type? 问题:如何在整个MetaContainer上调用getChildren()并将其传递给参数中任何类型的容器,等待它在容器类型的子类中寻址正确的getChildren()

EXPLANATION: In few words, I need to browse down the node-container in search for simple controls. 说明:简而言之,我需要向下浏览节点容器以搜索简单控件。 So you know not what type of node that is beforehand - only dynamically, when iterating. 因此,您不知道事先是什么类型的节点-迭代时只能动态地知道。 Some subnodes are containers that also have to be browsed, but each type requires specific approach. 一些子节点也是必须浏览的容器,但是每种类型都需要特定的方法。 I could do something like switch-case on types, but feel that should do something more elegant like subclasses for each type and one common interface. 我可以在类型上进行大小写切换,但是觉得应该做得更优雅一些,例如每种类型的子类和一个公共接口。

OK, let me take a stab at something, though I still don't know if I really understand the question. 好的,让我a一下,尽管我仍然不知道我是否真的理解这个问题。 I think you want to get a subset of the scene graph by getting child nodes of different Parent subclasses in different ways (ie not necessarily just by calling Parent.getChildrenUnmodifiable() ). 我认为您想通过以不同的方式获取不同Parent子类的子节点来获取场景图的子集(即不一定通过调用Parent.getChildrenUnmodifiable() )。 So if it's a simple Pane , you would just call getChildren() , but if it's a TabPane , you would get each Tab and get the content of each tab, forming a collection from those. 因此,如果它是一个简单的Pane ,则只需调用getChildren() ,但如果它是一个TabPane ,则将获取每个Tab并获取每个选项卡的内容,从而从中组成一个集合。 (And similarly for other "container-type controls", such as SplitPane , etc.) If it's a "simple" control, you don't regard it as having any child nodes (even though, behind the scenes, a Button contains a Text , for example). (和类似的其他“容器类型控件”,例如SplitPane等)。如果它是“简单”控件,则不要将其视为具有任何子节点(即使在幕后, Button包含一个例如Text )。

So I think you could perhaps do this by building a typesafe heterogeneous container (see Josh Bloch's Effective Java ) that mapped specific node types N to a Function<N, List<Node>> . 因此,我认为您可以通过构建类型安全的异构容器(请参阅Josh Bloch的Effective Java )将特定的节点类型N映射到Function<N, List<Node>>来做到这一点。 The function would define how to retrieve child nodes for that type. 该函数将定义如何检索该类型的子节点。

This might look like 这可能看起来像

public class ChildRetrievalMapping {

    public static final ChildRetrievalMapping DEFAULT_INSTANCE = new ChildRetrievalMapping() ;

    static {
        // note the order of insertion is important: start with the more specific type

        DEFAULT_INSTANCE.put(TabPane.class, tabPane -> 
                tabPane.getTabs().stream().map(Tab::getContent).collect(Collectors.toList()));
        DEFAULT_INSTANCE.put(SplitPane.class, SplitPane::getItems);
        // others...

        // default behavior for "simple" controls, just return empty list:
        DEFAULT_INSTANCE.put(Control.class, c -> Collections.emptyList());

        // default behavior for non-control parents, return getChildrenUnmodifiable:
        DEFAULT_INSTANCE.put(Parent.class, Parent::getChildrenUnmodifiable);


        // and for plain old node, just return empty list:
        DEFAULT_INSTANCE.put(Node.class, n -> Collections.emptyList());
    }

    private final Map<Class<?>, Function<? ,List<Node>>> map = new LinkedHashMap<>();

    public <N extends Node> void put(Class<N> nodeType, Function<N, List<Node>> childRetrieval) {
        map.put(nodeType, childRetrieval);
    }

    @SuppressWarnings("unchecked")
    public <N extends Node> Function<N, List<Node>> getChildRetrieval(Class<N> nodeType) {
        return (Function<N, List<Node>>) map.get(nodeType);
    }

    @SuppressWarnings("unchecked")
    public List<Node> firstMatchingList(Node n) {
        for (Class<?> type : map.keySet()) {
            if (type.isInstance(n)) {
                return getChildRetrieval((Class<Node>) type).apply(n);
            }
        }
        return Collections.emptyList();
    }
}

Now you can just call childRetrievalMapping.findFirstMatchingList(node); 现在您可以调用childRetrievalMapping.findFirstMatchingList(node); and it gets the list of children in the sense defined by the first type in the map which matches the node. 并从与节点匹配的地图中的第一种类型定义的意义上获取子级列表。 So, using the DEFAULT_INSTANCE , if you passed it a TabPane , it would get all the content nodes; 因此,使用DEFAULT_INSTANCE ,如果将TabPane传递给它,它将获取所有内容节点; if you passed it a SplitPane , it would get the items; 如果您将其传递给SplitPane ,它将获得项目; if you passed it another type of control, it would return an empty list, etc. 如果您将其传递给另一种类型的控件,它将返回一个空列表,依此类推。

Here's an example of using this. 这是使用此示例。 This just builds a scene graph, and then when you press the button, it traverses it, getting just the "simple" nodes defined by the strategies in the above class. 这只是建立一个场景图,然后当您按下按钮时,它会遍历它,只得到上述类中的策略定义的“简单”节点。 (And then it selects all instances of Labeled and passes the result of getText() to the system console.) Note how it (deliberately) avoids the labels that are part of the implementation of the tabs themselves, which a naïve root.lookupAll(".labeled") would not do. (然后,它选择Labeled所有实例,并将getText()的结果传递到系统控制台。)请注意,它如何(故意)避免了作为选项卡本身实现的一部分的标签,这是幼稚的root.lookupAll(".labeled")不会。

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Control;
import javafx.scene.control.Label;
import javafx.scene.control.Labeled;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class PerformActionOnNodeTypes extends Application {

    @Override
    public void start(Stage primaryStage) {
        VBox root = new VBox(5, 
                new Label("Label 1"),
                new HBox(5, new Label("Label 2"), new Button("Button 1")),
                new HBox(5, new TextField("Some text"), new ComboBox<String>()),
                new TabPane(new Tab("Tab 1", new VBox(new Label("Label in tab 1"))),
                        new Tab("Tab 2", new StackPane(new Button("Button in tab 2")))));

        Button button = new Button("Show labeled's texts");
        button.setOnAction(e -> {
            List<Node> allSimpleNodes = new ArrayList<>();
            findAllSimpleNodes(allSimpleNodes, root);
            doAction(allSimpleNodes, Labeled.class, (Labeled l) -> System.out.println(l.getText()));
        });

        root.setAlignment(Pos.CENTER);
        BorderPane.setAlignment(button, Pos.CENTER);
        Scene scene = new Scene(new BorderPane(root, null, null, button, null), 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private void findAllSimpleNodes(List<Node> allSimpleNodes, Node n) {
        List<Node> children = ChildRetrievalMapping.DEFAULT_INSTANCE.firstMatchingList(n);
        allSimpleNodes.addAll(children);
        for (Node child : children) {
            findAllSimpleNodes(allSimpleNodes, child);
        }
    }

    private <T> void doAction(Collection<Node> nodes, Class<T> type, Consumer<T> action) {
        nodes.stream()
            .filter(type::isInstance)
            .map(type::cast)
            .forEach(action);
    }

    public static class ChildRetrievalMapping {

        public static final ChildRetrievalMapping DEFAULT_INSTANCE = new ChildRetrievalMapping() ;

        static {
            // note the order of insertion is important: start with the more specific type

            DEFAULT_INSTANCE.put(TabPane.class, tabPane -> 
                    tabPane.getTabs().stream().map(Tab::getContent).collect(Collectors.toList()));
            DEFAULT_INSTANCE.put(SplitPane.class, SplitPane::getItems);
            // others...

            // default behavior for "simple" controls, just return empty list:
            DEFAULT_INSTANCE.put(Control.class, c -> Collections.emptyList());

            // default behavior for non-control parents, return getChildrenUnmodifiable:
            DEFAULT_INSTANCE.put(Parent.class, Parent::getChildrenUnmodifiable);


            // and for plain old node, just return empty list:
            DEFAULT_INSTANCE.put(Node.class, n -> Collections.emptyList());
        }

        private final Map<Class<?>, Function<? ,List<Node>>> map = new LinkedHashMap<>();

        public <N extends Node> void put(Class<N> nodeType, Function<N, List<Node>> childRetrieval) {
            map.put(nodeType, childRetrieval);
        }

        @SuppressWarnings("unchecked")
        public <N extends Node> Function<N, List<Node>> getChildRetrieval(Class<N> nodeType) {
            return (Function<N, List<Node>>) map.get(nodeType);
        }

        @SuppressWarnings("unchecked")
        public List<Node> firstMatchingList(Node n) {
            for (Class<?> type : map.keySet()) {
                if (type.isInstance(n)) {
                    return getChildRetrieval((Class<Node>) type).apply(n);
                }
            }
            return Collections.emptyList();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

I'm not sure if that's what you were wanting to do, and if so there may be more elegant ways to approach it. 我不确定这是否是您要执行的操作,如果是,可能会有更优雅的方法来处理。 But I think declaring a strategy like this for particular types is quite a bit nicer than a big switch on types, and it leaves the option of configuring it to have the specific rules you want. 但是我认为,针对特定类型声明这样的策略要比对类型进行大量更改要好得多,并且可以将其配置为具有所需的特定规则。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM