Расширение класса данных из закрытого класса в Котлине

У меня есть набор классов данных, которые разделяют некоторые общие поля. Поэтому в идеале я хотел бы объявить те в супертипе (Message в этом примере) и иметь возможность писать функции, которые работают с супертипом, если им нужен доступ к этим общим полям (messageId в этом примере).

fun operate(m: Message) {
  use(m.messageId)
}

Я попытался выполнить это, расширив классы данных из закрытого класса.

Классы данных могут распространять закрытые классы, но не я не уверен, как/если они могут принимать аргументы, требуемые закрытым классом супертипа.

  • Расширение обычного класса из закрытого класса компилируется просто отлично.

    sealed class Message(val messageId: String)
    
    class Track(val event: String, messageId: String): Message(messageId)
    
  • Однако его изменение в класс данных не компилируется ( "Основной конструктор класса данных должен иметь только параметры свойства (val/var)".).

    sealed class Message(val messageId: String)
    
    data class Track(val event: String, messageId: String): Message(messageId)
    
  • Объявление параметра как свойства также не компилируется ("'messageId' скрывает член супертипа 'Message' и нуждается в переопределении 'модификатора').

    sealed class Message(val messageId: String)
    
    data class Track(val event: String, val messageId: String): Message(messageId)
    
  • Открытие свойства супертипа и его переопределение в каждом из базовых классов компилируется отлично:

    sealed class Message(open val messageId: String)
    
    data class Track(val event: String, override val messageId: String): Message(messageId)
    

В идеале я хотел бы что-то близкое к Варианту 2 - он позволяет мне комбинировать лучшее из обоих миров.

В противном случае мне кажется, что мои параметры - либо переносить мои собственные функции класса данных (copy, hashcode, equals и т.д.) с помощью опции 1, либо жить с компромиссом, открыв свойства супертипа с опцией 4.

Ответ 1

Варианты 3 и 4 приведут к тому, что класс удерживает messageId дважды. Однажды в новом классе и один раз в своем суперклассе.

Решение состоит в объявлении, но не определении переменной в суперклассе:

sealed class Message {
    abstract val messageId: String
}

data class Track(val event: String, override val messageId: String): Message()

Это сделает messageId доступным на Message, но делегирует хранилище тем, что его реализует.