Как имитировать или достичь отношений 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
самостоятельно, если они вам нужны.