如何为 Kotlin 数据模仿或实现 IS-A 关系 类

How to mimic or achieve IS-A relationships for Kotlin data classes

我一直在探索 Kotlin 并写了一个小的 program/script 来完成我觉得很无聊的任务。

在程序开发中我使用数据classes来表示一个Playlist。在设计的某个时刻,我想要一种特殊类型的 Playlist,即 EmptyPlaylist

我无法让它工作。

您将如何与 Kotlin 建立这种关系?

在 Java 中,我会扩展 Playlist(或者可能创建一个 interface/abstract class 供它们继承)。

我只是想要 List<Playlist> 而不是 List<Playlist?>

最后我只是创建了一个 Playlist 对象,但我感兴趣的是是否可以使用数据 classes.

创建 IS-A 层次结构

UPD:Kotlin Beta 添加了对 data classes 的限制,因此答案也已更新。

  • Data classes cannot be abstract, open, sealed or inner;
  • Data classes may not extend other classes (but may implement interfaces).

因此构建层次结构的唯一选择是接口。此限制可能会在未来的版本中解除。


Playlist 继承 EmptyPlaylist 的想法根据你的话被认为是非空的看起来有点矛盾,但有选择:

  • 您可以让 TracksPlaylistEmptyPlaylist 实现一些 Playlist 接口。这是合理的,因为 EmptyPlaylist 可能不包含 Playlist 所做的一切。这看起来像:

    public interface Playlist
    
    data class TracksPlaylist(val name: String, val tracks: List<Track>) : Playlist
    data class EmptyPlaylist(val name: String): Playlist
    
  • 如果您不想 EmptyPlaylist 的多个实例甚至存在,您可以将其设为 val 而不是不同的 class 并避免 is-检查,只是比较

    val EMPTY_PLAYLIST = TracksPlaylist("EMPTY", listOf())
    

    这适用于空播放列表都相等,因此单个对象足以代表它们的情况。

  • 可以使用sealed classes。这不允许您使用 data classes,但是 sealed classes 可能适合构建这样的 class 层次结构。

    sealed class 是一个抽象 class ,它只能在其主体内声明其子class。这让编译器保证这些是唯一的 subclasses。示例:

    sealed class Playlist(val name: String) {
        class Tracks(name: String, val tracks: List<Track>): Playlist(name)
        class Playlists(name: String, val innerPlaylists: List<Playlist>): Playlist(name)
        object Empty: Playlist("EMPTY")
    }
    

    之后,如果你声明所有的子classes和sealed class的对象,你将可以使用when语句而不指定else分支:

    when (p) {
        is Playlist.Tracks -> { /* ... */ }
        is Playlist.Playlists -> { /* ... */ }
        Playlist.Empty -> { /* ... */ }
    }
    

    但是,这个选项需要你自己处理equalshashCodetoStringcopycomponentN,如果你需要的话他们。