Search code examples
oopkotlindesign-patternspolymorphismdynamic-binding

Dynamic binding for two arguments


This question concerns design patterns, and in particular dynamic binding, in statically typed languages (I use Kotlin here, but it could also be C++ or Java). The problem is as follows: I have an interface Node (representing nodes in an Ast) and concatenation of multiple elements (there are multiple such classes).

interface Node {
    fun concat(next: Node): Node {
        return Concat(listOf(this, next))
    }
}

class Concat(val nodes: List<Node>): Node {
}

I now want to make sure, that Concat is always flattened, ie, none of the nodes is a concatenation. This would be easy with some if(next is Concat) type check, but I would like to use dynamic binding and avoid such type checks. My first failing solution attempt would be the following:

interface Node {
    fun concat(next: Node): Node {
        return next.reverseConcat(this)
    }

    fun reverseConcat(prev: Node): Node {
        return Concat(prev, this)
    }
}

class Concat(val nodes: List<Node>): Node {
    override fun concat(next: Node): Node {
        // TODO what if next is a Concat?
        return Concat(nodes + next)
    }

    override fun reverseConcat(prev: Node): Node {
        // TODO what if prev is a Concat?
        return Concat(listOf(prev) + nodes) 
    }
}

but this fails if both nodes are instances of Concat. Another solution attempt would be to add reverseConcat-methods with Concat as parameters.

interface Node {
    // ...
    fun reverseConcatWithConcat(nextNodes: List<Node>): Node {
        return Concat(listOf(this) + nextNodes)
    }
}

class Concat(val nodes: List<Node>): Node {
    override fun concat(next: Node): Node {
        return next.reverseConcatWithConcat(nodes)
    }

    override fun reverseConcat(prev: Node): Node {
        // TODO what if prev is a Concat?
        return Concat(listOf(prev) + nodes) 
    }

    fun reverseConcatWithConcat(nextNodes: List<Node>): Node {
        return Concat(nodes + nextNodes)
    }
}

This would work but it clutters the interface (consider that there are other nodes, similar to Concat) and it also leaves the problem that there are no protected methods in interfaces so that reverseConcat is still dangerous.

Is there a more satisfying approach using dynamic binding that does not clutter the code unnecessarily?


Solution

  • Usually I would do something like this, because it's performant (although I would provide multi-concat):

    interface Node {
        fun forContents(proc: (Node) -> Unit) {
            proc(this)
        }
        // I don't actually like this signature, but it's what you wanted
        fun concat(next: Node) : Node {
            val list = ArrayList<Node>()
            forContents(list::add)
            next.forContents(list::add)
            return Concat(list)
        }
    }
    
    class Concat(val contents: List<Node>) : Node {
        override fun forContents(proc: (Node) -> Unit) {
            contents.forEach(proc)
        }
    }
    

    You could also do this:

    interface Node {
        val contents: List<Node> get() = listOf(this)
        
        fun concat(next: Node) : Node = Concat(
            listOf(this.contents, next.contents).flatten()
        )
    }
    
    class Concat(override val contents: List<Node>) : Node {
    
    }