java 如何避免多重继承
How to avoid multiple inheritance in java
我正处于一种情况,我正在尝试实现一个(相对简单的)抽象语法树。所有节点都继承自一个名为 SimpleNode 的类型,其中包含一些用于存储行和列信息并接受访问者的代码。
现在,一些节点也应该是可命名的,而其他节点应该有 属性 "accessible"(例如 public 或私有)。有些节点甚至应该支持这两种接口。
我最好使用虚拟继承来实现它并编写两个 类 NameableNode 和 AccessibleNode,但是 Java 不支持 MI。
例如,NameableNode 可能有字段 "name" 并为此字段实现简单的 getter 和 setter。同样,AccessibleNode 也可能有字段 "accessibility" 和 getters/setters.
什么是实现这一点并避免在代码库的大部分中引入代码重复的好方法?
小代码示例:
public class SimpleNode {
private int line = 0;
private int column = 0;
/* Getters and setters for line/column. */
/* ... */
}
public class NameableNode extends SimpleNode {
private String name = "";
/* Getters and setters for name */
}
public class AccessibleNode extends SimpleNode {
private boolean isPublic = false;
/* Getters and setters for accessibility */
}
在这种情况下,我会使用组合方法而不是继承:
public class Node {
private int line = 0;
private int column = 0;
/* Getters and setters for line/column. */
/* ... */
private String name = null;
public String getName() {
return this.name;
}
public void setName(String name) {
this._name = name;
}
private Boolean _isPublic = null;
public String isPublic() {
return this.name;
}
public void setIsPublic(boolean isPublic) {
this._isPublic = isPublic;
}
public boolean hasAccessibility() {
return this._isPublic != null;
}
public boolean hasName() {
return this._name != null;
}
}
我更喜欢的另一种解决方案是使用 HashMap 和指示节点所有可能属性的枚举动态创建这些属性。这种方式更通用,因为它需要编写更少的代码来支持新属性,但它的类型安全性(ish)也较低,因为需要在运行时转换额外的属性:
import java.util.HashMap;
enum NodeAttribute {
NAME,
ACCESSIBILTY
}
enum NodeAccessibility {
PUBLIC,
PRIVATE
}
public class Node {
private int line = 0;
private int column = 0;
// Notice that this Object usage might involve some boxing for attributes of premitive type
private HashMap<NodeAttribute, Object> additionalAttributes = new HashMap<NodeAttribute, Object>();
/* Getters and setters for line/column. */
/* ... */
public boolean hetAttribute(NodeAttribute attribute) {
return this.additionalAttributes.containsKey(attribute);
}
public <T> T getAttributeValue(NodeAttribute attribute, Class<T> attributeClass) {
Object attributeValue = this.additionalAttributes.get(attribute);
// You may want to wrap the ClassCastException that may be raisen here to a more specfic error
T castedAttributeValue = attributeClass.cast(attributeValue);
return castedAttributeValue;
}
public void setAttributeValue(NodeAttribute attribute, Object value) {
// Notice that this implemintation allows changing the type of an existing attribute,
// If this is invalid behavior in your case you can throw an exception instead
this.additionalAttributes.put(attribute, value);
}
}
// Example usage
public class Program {
public static void main(String[] args) {
Node nodeWithNameOnly = new Node();
nodeWithNameOnly.setAttributeValue(NodeAttribute.NAME, 'node1');
Node nodeWithBoth = new Node();
nodeWithBoth.setAttributeValue(NodeAttribute.NAME, 'node2');
nodeWithBoth.setAttributeValue(NodeAttribute.ACCESSIBILTY, NodeAccessibility.PRIVATE);
Program.doStuffWithNode(nodeWithNameOnly);
/* output:
Node name: node1
*/
Program.doStuffWithNode(nodeWithBoth);
/* output:
Node name: node2
Node is public: False
*/
}
public static void doStuffWithNode(Node node) {
if (nodeWithNameOnly.hetAttribute(NodeAttribute.NAME)) {
String nodeName = nodeWithNameOnly.getAttributeValue(NodeAttribute.NAME, String.class);
system.out.println("Node name: " + nodeName);
}
if (nodeWithNameOnly.hetAttribute(NodeAttribute.ACCESSIBILTY)) {
NodeAccessibility nodeAccessibilty =
nodeWithNameOnly.getAttributeValue(NodeAttribute.ACCESSIBILTY, NodeAccessibility.class);
boolean nodeIsPublic = nodeAccessibilty == NodeAccessibility.PUBLIC;
system.out.println("Node is public: " + String.valueOf(nodeIsPublic));
}
}
}
无论如何,这是主要的经验法则 - 继承应用于 "is a" 关系,而组合应用于 "has a" 关系。
例如:
- 鱼延伸动物,因为鱼是动物。
- Post 保留 条评论,因为 Post 有 条评论。
在我们的例子中,节点 具有 名称和可访问性级别,因此它应该包含它们。
您正在寻找 composition。这有很多风格 - 根据我对您要构建的内容的理解,我会提出一个应该适合您的目的。
首先,让我们为您创建一些界面 Node
s:
public interface Nameable {
/* Getters and setters for name */
}
public interface Accessible {
/* Getters and setters for accessibility */
}
接下来,您可能不想为每个 Node
重复相同的实现,所以让我们创建这些实现:
public class NameDelegate() {
private String name = "";
/* Getters and setters for name */
}
public class AccessDelegate() {
private boolean isPublic = false;
/* Getters and setters for accessibility */
}
现在,让我们把所有东西放在一起:
public class SomeNodeA extends SimpleNode implements Nameable {
private NameDelegate nameDelegate;
public SomeNodeA(NameDelegate nameDelegate) {
this.nameDelegate = nameDelegate;
}
@Override
public String getName() {
return nameDelegate.getName();
}
@Override
public String setName(String name) {
nameDelegate.setName(name);
}
}
您也可以将两种行为都集中在一个 class:
public class SomeNodeB extends SimpleNode implements Nameable, Accessible {
private NameDelegate nameDelegate;
private AccessDelegate accessDelegate;
public SomeNodeB(NameDelegate nameDelegate, AccessDelegate accessDelegate) {
this.nameDelegate = nameDelegate;
this.accessDelegate = accessDelegate;
}
@Override
public String getName() {
return nameDelegate.getName();
}
@Override
public String setName(String name) {
nameDelegate.setName(name);
}
@Override
public boolean getAccessibility() {
return accessDelegate.getAccessibility();
}
/* etc... */
}
想法是,您可以将不同 "features" 的状态和功能打包到单独的委托中,并将它们作为节点中的相应接口公开。
此外,在 Node
上操作时,如果您需要知道 Node
的给定实例是否支持特定功能,您可以使用 instanceof
- 例如:
if (someNode instanceof Nameable) {
// do naming stuff
}
我正处于一种情况,我正在尝试实现一个(相对简单的)抽象语法树。所有节点都继承自一个名为 SimpleNode 的类型,其中包含一些用于存储行和列信息并接受访问者的代码。
现在,一些节点也应该是可命名的,而其他节点应该有 属性 "accessible"(例如 public 或私有)。有些节点甚至应该支持这两种接口。
我最好使用虚拟继承来实现它并编写两个 类 NameableNode 和 AccessibleNode,但是 Java 不支持 MI。
例如,NameableNode 可能有字段 "name" 并为此字段实现简单的 getter 和 setter。同样,AccessibleNode 也可能有字段 "accessibility" 和 getters/setters.
什么是实现这一点并避免在代码库的大部分中引入代码重复的好方法?
小代码示例:
public class SimpleNode {
private int line = 0;
private int column = 0;
/* Getters and setters for line/column. */
/* ... */
}
public class NameableNode extends SimpleNode {
private String name = "";
/* Getters and setters for name */
}
public class AccessibleNode extends SimpleNode {
private boolean isPublic = false;
/* Getters and setters for accessibility */
}
在这种情况下,我会使用组合方法而不是继承:
public class Node {
private int line = 0;
private int column = 0;
/* Getters and setters for line/column. */
/* ... */
private String name = null;
public String getName() {
return this.name;
}
public void setName(String name) {
this._name = name;
}
private Boolean _isPublic = null;
public String isPublic() {
return this.name;
}
public void setIsPublic(boolean isPublic) {
this._isPublic = isPublic;
}
public boolean hasAccessibility() {
return this._isPublic != null;
}
public boolean hasName() {
return this._name != null;
}
}
我更喜欢的另一种解决方案是使用 HashMap 和指示节点所有可能属性的枚举动态创建这些属性。这种方式更通用,因为它需要编写更少的代码来支持新属性,但它的类型安全性(ish)也较低,因为需要在运行时转换额外的属性:
import java.util.HashMap;
enum NodeAttribute {
NAME,
ACCESSIBILTY
}
enum NodeAccessibility {
PUBLIC,
PRIVATE
}
public class Node {
private int line = 0;
private int column = 0;
// Notice that this Object usage might involve some boxing for attributes of premitive type
private HashMap<NodeAttribute, Object> additionalAttributes = new HashMap<NodeAttribute, Object>();
/* Getters and setters for line/column. */
/* ... */
public boolean hetAttribute(NodeAttribute attribute) {
return this.additionalAttributes.containsKey(attribute);
}
public <T> T getAttributeValue(NodeAttribute attribute, Class<T> attributeClass) {
Object attributeValue = this.additionalAttributes.get(attribute);
// You may want to wrap the ClassCastException that may be raisen here to a more specfic error
T castedAttributeValue = attributeClass.cast(attributeValue);
return castedAttributeValue;
}
public void setAttributeValue(NodeAttribute attribute, Object value) {
// Notice that this implemintation allows changing the type of an existing attribute,
// If this is invalid behavior in your case you can throw an exception instead
this.additionalAttributes.put(attribute, value);
}
}
// Example usage
public class Program {
public static void main(String[] args) {
Node nodeWithNameOnly = new Node();
nodeWithNameOnly.setAttributeValue(NodeAttribute.NAME, 'node1');
Node nodeWithBoth = new Node();
nodeWithBoth.setAttributeValue(NodeAttribute.NAME, 'node2');
nodeWithBoth.setAttributeValue(NodeAttribute.ACCESSIBILTY, NodeAccessibility.PRIVATE);
Program.doStuffWithNode(nodeWithNameOnly);
/* output:
Node name: node1
*/
Program.doStuffWithNode(nodeWithBoth);
/* output:
Node name: node2
Node is public: False
*/
}
public static void doStuffWithNode(Node node) {
if (nodeWithNameOnly.hetAttribute(NodeAttribute.NAME)) {
String nodeName = nodeWithNameOnly.getAttributeValue(NodeAttribute.NAME, String.class);
system.out.println("Node name: " + nodeName);
}
if (nodeWithNameOnly.hetAttribute(NodeAttribute.ACCESSIBILTY)) {
NodeAccessibility nodeAccessibilty =
nodeWithNameOnly.getAttributeValue(NodeAttribute.ACCESSIBILTY, NodeAccessibility.class);
boolean nodeIsPublic = nodeAccessibilty == NodeAccessibility.PUBLIC;
system.out.println("Node is public: " + String.valueOf(nodeIsPublic));
}
}
}
无论如何,这是主要的经验法则 - 继承应用于 "is a" 关系,而组合应用于 "has a" 关系。
例如:
- 鱼延伸动物,因为鱼是动物。
- Post 保留 条评论,因为 Post 有 条评论。
在我们的例子中,节点 具有 名称和可访问性级别,因此它应该包含它们。
您正在寻找 composition。这有很多风格 - 根据我对您要构建的内容的理解,我会提出一个应该适合您的目的。
首先,让我们为您创建一些界面 Node
s:
public interface Nameable {
/* Getters and setters for name */
}
public interface Accessible {
/* Getters and setters for accessibility */
}
接下来,您可能不想为每个 Node
重复相同的实现,所以让我们创建这些实现:
public class NameDelegate() {
private String name = "";
/* Getters and setters for name */
}
public class AccessDelegate() {
private boolean isPublic = false;
/* Getters and setters for accessibility */
}
现在,让我们把所有东西放在一起:
public class SomeNodeA extends SimpleNode implements Nameable {
private NameDelegate nameDelegate;
public SomeNodeA(NameDelegate nameDelegate) {
this.nameDelegate = nameDelegate;
}
@Override
public String getName() {
return nameDelegate.getName();
}
@Override
public String setName(String name) {
nameDelegate.setName(name);
}
}
您也可以将两种行为都集中在一个 class:
public class SomeNodeB extends SimpleNode implements Nameable, Accessible {
private NameDelegate nameDelegate;
private AccessDelegate accessDelegate;
public SomeNodeB(NameDelegate nameDelegate, AccessDelegate accessDelegate) {
this.nameDelegate = nameDelegate;
this.accessDelegate = accessDelegate;
}
@Override
public String getName() {
return nameDelegate.getName();
}
@Override
public String setName(String name) {
nameDelegate.setName(name);
}
@Override
public boolean getAccessibility() {
return accessDelegate.getAccessibility();
}
/* etc... */
}
想法是,您可以将不同 "features" 的状态和功能打包到单独的委托中,并将它们作为节点中的相应接口公开。
此外,在 Node
上操作时,如果您需要知道 Node
的给定实例是否支持特定功能,您可以使用 instanceof
- 例如:
if (someNode instanceof Nameable) {
// do naming stuff
}