So basically I have a abstract class representing a BinaryTree below. I want to create a subclass called RBTree which will take advantage of all the abstraction in BinaryTree including the ability to modify the inner Node class to take on a color and parent property.
However I can't figure out how Kotlin or Java do this if they can even at all.
The closest I'm able to come is creating a new class that's a subclass of Node which ruins a lot of the other abstraction in BinaryTree since we have to use different Node objects. Trying to just redefine Node just doesn't work. If you know how to do it in java that would work too.
class RBTree<E : Comparable<E>> : BinaryTree<E>(){
override val rootNode: RBNode<E> = TODO()
override fun addNode(node: Node<E>) { //since we have a new class RBNode this doesn't work
TODO()
}
override fun removeNode(): Node<E> { //same problem
TODO()
}
inner class RBNode<E>(data: E, var color: NodeColor, var parent: RBNode<E>?, left: RBNode<E>? = null, right: RBNode<E>? = null) : Node<E>(data, left, right){
fun switchColor() : NodeColor{
color = when (color) {
NodeColor.RED -> NodeColor.BLACK
else -> NodeColor.RED
}
return color
}
}
}
enum class NodeColor {
RED,
BLACK
}
abstract class BinaryTree<E : Comparable<E>>() {
abstract val rootNode: Node<E>
abstract fun addNode(node : Node<E>)
abstract fun removeNode() : Node<E>
abstract inner class Node<E : Comparable<E>>(
private val data: E,
private val left: Node<E>? = null,
private val right: Node<E>? = null) : Comparable<Node<E>> {
fun isLeaf(): Boolean{
return (left == null && right == null)
}
override fun compareTo(other: Node<E>): Int {
return data.compareTo(other.data)
}
}
}
This is not really a limitation of Java or Kotlin. This case is tricky even conceptually. Let's think about it for a while.
We have a common "family" of objects. Then we have a more specialized family which accepts working with their own family only.
RBTreedoesn't accept working with just anyNode, it requiresRBNodespecifically, for example inaddNode(). That meansRBTreeeffectively is not a subtype ofBinaryTree, because it can't be used as aBinaryTree.There are multiple ways to tackle this problem, depending on our specific case.
If we construct the whole graph initially and then we only work with it, but we don't mutate it anymore, then we can separate read-only and mutable interfaces. Only this initial code for constructing the graph will have to make sure
RBTreecorrectly gotRBNode. ThenRBTreecan be safely treated as a read-onlyBinaryTreeand depending if we useRBTreeorBinaryTree, we see nodes as eitherRBNodeorNode.Another approach, very specific to some cases is to adapt
Nodeto be theRBNode. In this solutionRBTreeallows to add regularNodeobjects to it, but it wraps them in its own classes and when asked back, it returns a wrappedRBNode. This approach also allowsRBTreeto be a subtype of theBinaryTree.Third solution is to parameterize the tree by its node type. We have to introduce
N : Node<N, E>in multiple places around the code:This way we ensure
RBTreeworks withRBNodeonly. Please noteRBTreeis no longer a subtype ofBinaryTree<Node>. Instead, it is a subtype ofBinaryTree<RBNode>.I removed
innerfrom nodes as it seems this is not needed. If this is in fact required, then the case is a little more complicated, because nodes are implicitly typed by the tree as well.