Несколько конструкторов в неизменяемом классе (data)


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

data class Color(val r: Int, val g: Int, val b: Int) {
   constructor(hex: String) {
        assert(Regex("#[a-fA-F0-6]{6}").matches(hex), { "$hex is not a hex color" } )
        val r = hex.substring(1..2).toInt(16)
        val g = hex.substring(3..4).toInt(16)
        val b = hex.substring(5..6).toInt(16)
        this(r,g,b)
    }
}

Конечно, это не так: Котлин ожидает, что вызов главного конструктора будет объявлен сверху:

constructor(hex: String): this(r,g,b) {
    assert(Regex("#[a-fA-F0-6]{6}").matches(hex), { "$hex is not a hex color" } )
    val r = hex.substring(1..2).toInt(16)
    val g = hex.substring(3..4).toInt(16)
    val b = hex.substring(5..6).toInt(16)
}

Это тоже нехорошо, так как вызов выполняется перед телом конструктора и не может получить доступ к локальным переменным.

Я могу сделать это , Конечно:

constructor(hex: String): this(hex.substring(1..2).toInt(16),
                               hex.substring(3..4).toInt(16), 
                               hex.substring(5..6).toInt(16)) {
    assert(Regex("#[a-fA-F0-6]{6}").matches(hex), { "$hex is not a hex color" } )
}

Но это проверит утверждение слишком поздно и масштабируется не очень хорошо.

Единственный способ, который я вижу, чтобы приблизиться к желаемому поведению, это использование вспомогательной функции (которая не может быть определена нестатически на Color):

constructor(hex: String): this(hexExtract(hex, 1..2), 
                               hexExtract(hex, 3..4), 
                               hexExtract(hex, 5..6))
Это не кажется мне очень элегантным рисунком, поэтому я предполагаю, что здесь что-то упускаю. Существует ли элегантный идиоматический способ иметь (сложные) вторичные конструкторы на неизменяемых классах данных в Kotlin?
2 2

2 ответа:

Как предложил @nhaarman, один из способов - использовать фабричный метод. Я часто использую что-то вроде следующего:

data class Color(val r: Int, val g: Int, val b: Int) {
   companion object {
        fun fromHex(hex: String): Color {
            assert(Regex("#[a-fA-F0-6]{6}").matches(hex), { "$hex is not a hex color" } )
            val r = hex.substring(1..2).toInt(16)
            val g = hex.substring(3..4).toInt(16)
            val b = hex.substring(5..6).toInt(16)
            return Color(r,g,b)
        }
    }
}

И затем вы можете вызвать его с помощью Color.fromHex("#abc123")

Как объяснено здесь , используя функцию оператора invoke на сопутствующем объекте (так же, как Scala apply) , можно получить не настоящий конструктор, а фабрику, которая выглядит как конструктор usage-site:

companion object {
    operator fun invoke(hex: String) : Color {
        assert(Regex("#[a-fA-F0-6]{6}").matches(hex),
               {"$hex is not a hex color"})
        val r = hex.substring(1..2).toInt(16)
        val g = hex.substring(3..4).toInt(16)
        val b = hex.substring(5..6).toInt(16)
        return Color(r, g, b)
    }
}

Теперь, Color("#FF00FF") воля к ожидаемой вещи.