How to handle polymorphism in kotlin

Viewed 1483

I am working with an abstract class and two concrete ones, which implementing the abstract one. The diagram is as the next: enter image description here

My classes looks as:

abstract class NavItem() {
    var attributes: String = ""
    var text = ""
}

class NavMenu(val items: MutableList<NavItem>) : NavItem()

class NavLink(var shortText: String) : NavItem()

The problem is when I try to work with the items which could be NavMenu or NavLinks, the NavMenus has a collection of NavLinks.

I am trying to work with the items using polymorphism as the next code:

navMenu.items.forEach{ item ->
            buildNavItem(item)
        }

the buildNavItem methods seems as:

private fun buildNavItem(navMenu: NavMenu){
        navMenu.items
        navMenu.attributes
        navMenu.items
    }

    private fun buildNavItem(navItem: NavItem){
        navItem.text
        navItem.attributes
    }

    private fun buildNavItem(navLink: NavLink){
        navLink.text
        navLink.attributes
    }

But the code is always getting into buildNavItem(navItem: NavItem), even when in the for each I can see sometimes that the item is NavLink, or is NavMenu.

Any suggestion?

Thanks!!

2 Answers

That is not how polymorphism works. You have navMenu.items list that is the type of MutableList<NavItem>, it can store NavItems or its descendants. In forEach function you go through each item, which has NavItem type, and call buildNavItem(item) function. In this case buildNavItem(navItem: NavItem) is always called. To call the same method with another parameter you need to explicitly cast it to that type. What I recommend, and that's how polymorphism works, is to create buildNavItem() function in NavItem class and implement it in descendants:

abstract class NavItem() {
    var attributes: String = ""
    var text = ""
    abstract fun buildNavItem()
}

class NavMenu(val items: MutableList<NavItem>) : NavItem() {
    override fun buildNavItem() {
        // ... your concrete implementation for NavMenu
    }
}

class NavLink(var shortText: String) : NavItem() {
    override fun buildNavItem() {
        // ... your concrete implementation for NavLink
    }
}

And then you can call it in forEach function:

navMenu.items.forEach { item ->
        item.buildNavItem()
}

In that case buildNavItem() function will be called for the right object, that is stored in navMenu.items, i.e. if it is an object of NavLink type then function 'buildNavItem()', overridden in NavLink class, will be called.

Problem:

navMenu.items.forEach { item ->
    item.buildNavItem()
}

Since items is of type List<NavMenu>, the compiler will call the function which is appropriate for an item of type NavMenu, in this case the overload which takes a NavMenu.

Solution:

In order to call a more specific overload the compiler needs to know the type. You can smart cast the items an the appropriate function will be called:

private fun buildNavItem(navMenu: NavMenu) {
   when(navMenu){
      is NavItem -> buildNavItem(navMenu) // navMenu is smart casted to NavItem
      is NavLink -> buildNavItem(navMenu) // navMenu is smart casted to NavLink
      else -> throw IllegalStateException("Unknown subtype ${navMenu::class.simpleName} of NavMenu")
   }
}

This way whenever you call buildNavItem, you delegate to the appropriate function.

Related