LY Corporation Tech Blog

We are promoting the technology and development culture that supports the services of LY Corporation and LY Corporation Group (LINE Plus, LINE Taiwan and LINE Vietnam).

This post is also available in the following languages. Japanese

Improving code quality - Session 24: The value of inheritance

Hello, I'm Munetoshi Ishikawa, a mobile client developer for the LINE messaging app.

This article is the latest installment of our weekly series "Improving code quality". For more information about the Weekly Report, please see the first article.

The value of inheritance

Let's say there's a class called FooScreen with UI elements like buttons and text, and the colors and images of these UI elements change based on a "theme". To implement this, the following code defines an interface called FooScreenThemeStrategy.

class FooScreen {
    private val okButton: Button = ...
    private val cancelButton: Button = ...
    private val mainDescription: TextView = ...
    ...

    fun bindTheme(strategy: FooScreenThemeStrategy) {
        okButton.bindTheme(strategy)
        cancelButton.bindTheme(strategy)

        mainDescription.textColor = strategy.textColorInt
        ...
    }

    companion object {
        private fun Button.bindTheme(strategy: FooScreenThemeStrategy) {
            buttonColor = strategy.buttonColorInt
            textColor = strategy.textColorInt
        }
    }
}

interface FooScreenThemeStrategy {
    val backgroundColorInt: Color
    val textColorInt: Color
    val buttonColorInt: Color

    val headerIcon: Image
    val errorIcon: Image
    val checkMarkIcon: Image
}

The interface FooScreenThemeStrategy only defines the elements available for a theme, and the actual values are determined through inheritance as shown below.

class FooScreenLightTheme : FooScreenThemeStrategy {
    override val backgroundColorInt: Color = Color.WHITE
    override val textColorInt: Color = Color.DARK_GRAY
    override val buttonColorInt: Color = Color.LIGHT_GREEN

    override val headerIcon: Image = GREEN_HEADER_ICON
    override val errorIcon: Image = RED_ERROR_ICON
    override val checkMarkIcon: Image = GREEN_CHECK_MARK_ICON
}

class FooScreenDarkTheme : FooScreenThemeStrategy {
    override val backgroundColorInt: Color = Color.BLACK
    override val textColorInt: Color = Color.OFF_WHITE
    override val buttonColorInt: Color = Color.DARK_GRAY

    override val headerIcon: Image = OFF_WHITE_HEADER_ICON
    override val errorIcon: Image = OFF_WHITE_ERROR_ICON
    override val checkMarkIcon: Image = OFF_WHITE_CHECK_MARK_ICON
}

Is there anything that can be improved in this code?

The choice not to inherit

In many cases, when only the values differ and the logic is the same, inheritance isn't necessary.

Typical cases where inheritance is needed include:

  • Changing logic dynamically (using dynamic dispatch)
  • Implementing a sum type (like Java or Kotlin's sealed class)
  • Separating implementation from interface (adapting to framework constraints, using DI containers, speeding up builds, etc.)
  • Applying the dependency inversion principle (resolving circular dependencies, making dependencies unidirectional)

For purposes other than these, it's often simpler to implement without using inheritance. For example, for simple logic sharing, you can use aggregation or composition.

What FooScreenThemeStrategy needs is close to "changing logic dynamically". However, in reality, there's no need to change the logic; just changing the values is enough. In this case, you can implement it without inheritance by defining a class that holds the values instead of using an interface.

class FooScreenThemeModel(
    val backgroundColorInt: Color,
    val textColorInt: Color,
    val buttonColorInt: Color,
    val headerIcon: Image,
    val errorIcon: Image,
    val checkMarkIcon: Image
)

val FOO_SCREEN_LIGHT_THEME_MODEL = FooScreenThemeModel(
    backgroundColorInt = Color.WHITE,
    textColorInt = Color.DARK_GRAY,
    buttonColorInt = Color.LIGHT_GREEN,
    headerIcon = GREEN_HEADER_ICON,
    errorIcon = RED_ERROR_ICON,
    checkMarkIcon = GREEN_CHECK_MARK_ICON,
)

Especially in Kotlin, unless you add open, classes can't be inherited. (This is equivalent to final class in Java.) By defining FooScreenThemeModel as a non-inheritable class instead of an interface, you can guarantee two things:

  1. (If the property values are immutable) Each property won't change dynamically
  2. There's no instance-specific (unique) logic

In a nutshell

When only the values differ and the logic is the same, create instances with different values instead of using inheritance.

Keywords: inheritance, instantiation, dynamic dispatch

List of articles on techniques for improving code quality