Как имитировать или достичь отношений IS-A для классов данных Kotlin

Я изучал Kotlin и написал небольшую программу / скрипт, который выполняет задачу, которую я нахожу скучной.

При разработке программы я использую классы данных для представления списка воспроизведения. в какой-то момент в дизайне я хотел иметь особый тип Playlist это было EmptyPlaylist,

Я не мог заставить это работать.

Как бы вы достигли этих отношений с Kotlin?

На Java я бы просто продлил Playlist (или, возможно, создайте интерфейс / абстрактный класс для них обоих наследовать).

Я просто хотел иметь возможность List<Playlist> скорее, чем List<Playlist?>

В конце концов я просто создал Playlist объект, но меня интересует, возможно ли создание иерархий IS-A с классами данных.

1 ответ

Решение

UPD: Kotlin Beta добавил ограничения на data классы, поэтому ответ также был обновлен.

  • Классы данных не могут быть абстрактными, открытыми, запечатанными или внутренними;
  • Классы данных могут не расширять другие классы (но могут реализовывать интерфейсы).

Таким образом, единственный вариант построения их иерархий - это интерфейсы. Это ограничение может быть выпущено в будущих версиях.


Идея наследования EmptyPlaylist от Playlist который, по вашим словам, считается не пустым, выглядит несколько противоречивым, но есть варианты:

  • Вы можете сделать как TracksPlaylist а также EmptyPlaylist реализовать некоторые 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 вместо отдельного класса и избегать проверок is, просто сравнивая с

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

    Это применимо, когда все пустые списки воспроизведения равны, так что одного объекта достаточно, чтобы представить их все.

  • Ты можешь использовать sealed классы. Это не позволяет вам использовать data классы, но sealed классы могут подходить для построения иерархий классов, как это.

    sealed class является абстрактным классом, который может иметь только свои подклассы, которые могут быть объявлены внутри его тела. Это дает компилятору гарантии того, что это единственные подклассы. Пример:

    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")
    }
    

    После этого вы сможете использовать when заявление без указания else ветвь, если вы указали все подклассы и объекты запечатанного класса:

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

    Однако этот вариант требует от вас иметь дело с equals, hashCode, toString, copy а также componentN самостоятельно, если они вам нужны.

Другие вопросы по тегам