Reputation: 397
I am in a situation, where I'm trying to implement a (relatively simple) abstract syntax tree. All of the nodes inherit from a type called SimpleNode containing some code to store line and column information and accepting a visitor.
Now, some of the nodes should also be nameable, while others should have a property "accessible" (eg. public or private). Some nodes should even support both interfaces.
I'd preferably implement this using virtual inheritance and write two classes NameableNode and AccessibleNode, but Java doesn't support MI.
Eg NameableNode might have field "name" and implement simple getters and setters for this field. Similarly, AccessibleNode might also have a field "accessibility" and getters/setters.
What is a good way to implement this and avoid introducing code duplication in a huge part of the code base?
Small code example:
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 */
}
Upvotes: 2
Views: 592
Reputation: 3980
You're looking for composition. There are many flavors of this - I will propose one that, from my understanding of what you're trying to build, should suit your purpose.
First, let's create some interfaces for yours Node
s:
public interface Nameable {
/* Getters and setters for name */
}
public interface Accessible {
/* Getters and setters for accessibility */
}
Next, you probably don't want to repeat the same implementation for every Node
, so let's create those implementations:
public class NameDelegate() {
private String name = "";
/* Getters and setters for name */
}
public class AccessDelegate() {
private boolean isPublic = false;
/* Getters and setters for accessibility */
}
Now, let's put everything together:
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);
}
}
You can also have both behaviours in a single 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... */
}
The idea is, you can package the state and the functionality of the different "features" into individual delegates, and expose them as corresponding interfaces in your Nodes.
Also, when operating on the Node
s, if you need to know whether a given instance of a Node
supports a specific feature, you can use instanceof
- e.g.:
if (someNode instanceof Nameable) {
// do naming stuff
}
Upvotes: 2
Reputation: 28
In this case I would use the composition approach over inheritance:
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;
}
}
Another solution that I like a bit more is creating these attributes dynamically using a HashMap and an enum that indicates all the possible attributes of a node. This way is more generic, as it requires to write less code for supporting new attributes, But it is also less typesafe(ish), as the additional attributes need to be casted at runtime:
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));
}
}
}
In any case, this is the main rule of thumb - Inheritance should be used for an "is a" relation, whereas composition should be used for an "has a" relation.
For instance:
And in our case, a node has a name and an accessibility level so it should hold them.
Upvotes: 0