[英]Call Subclasses' Method Depending on Object Type in Java
问题:根据传递的参数类型,动态选择相应的子类并调用其方法。
研究:努力将泛型方法应用于任务:
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.
}
}
}
我尝试使用这样的类(并且会出错,因为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;
}
问题:如何在整个MetaContainer
上调用getChildren()
并将其传递给参数中任何类型的容器,等待它在容器类型的子类中寻址正确的getChildren()
?
说明:简而言之,我需要向下浏览节点容器以搜索简单控件。 因此,您不知道事先是什么类型的节点-迭代时只能动态地知道。 一些子节点也是必须浏览的容器,但是每种类型都需要特定的方法。 我可以在类型上进行大小写切换,但是觉得应该做得更优雅一些,例如每种类型的子类和一个公共接口。
好的,让我a一下,尽管我仍然不知道我是否真的理解这个问题。 我认为您想通过以不同的方式获取不同Parent
子类的子节点来获取场景图的子集(即不一定通过调用Parent.getChildrenUnmodifiable()
)。 因此,如果它是一个简单的Pane
,则只需调用getChildren()
,但如果它是一个TabPane
,则将获取每个Tab
并获取每个选项卡的内容,从而从中组成一个集合。 (和类似的其他“容器类型控件”,例如SplitPane
等)。如果它是“简单”控件,则不要将其视为具有任何子节点(即使在幕后, Button
包含一个例如Text
)。
因此,我认为您可以通过构建类型安全的异构容器(请参阅Josh Bloch的Effective Java )将特定的节点类型N
映射到Function<N, List<Node>>
来做到这一点。 该函数将定义如何检索该类型的子节点。
这可能看起来像
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();
}
}
现在您可以调用childRetrievalMapping.findFirstMatchingList(node);
并从与节点匹配的地图中的第一种类型定义的意义上获取子级列表。 因此,使用DEFAULT_INSTANCE
,如果将TabPane
传递给它,它将获取所有内容节点; 如果您将其传递给SplitPane
,它将获得项目; 如果您将其传递给另一种类型的控件,它将返回一个空列表,依此类推。
这是使用此示例。 这只是建立一个场景图,然后当您按下按钮时,它会遍历它,只得到上述类中的策略定义的“简单”节点。 (然后,它选择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);
}
}
我不确定这是否是您要执行的操作,如果是,可能会有更优雅的方法来处理。 但是我认为,针对特定类型声明这样的策略要比对类型进行大量更改要好得多,并且可以将其配置为具有所需的特定规则。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.