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:
- (If the property values are immutable) Each property won't change dynamically
- 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