Как применить функцию к значению, определенному в конструкторе класса данных перед методом init? - PullRequest
0 голосов
/ 28 июня 2018

Допустим, у меня есть класс данных, подобный этому:

data class MyData(val something: Int, val somethingElse : String) {
    init {
        require(something > 20) { "Something must be > 20" }
        require(StringUtils.isNotEmtpy(somethingElse)) { "Something else cannot be blank" }
    }
}

Я бы хотел иметь возможность применить функцию к somethingElse до вызова метода init. В этом случае я хочу удалить все символы \n из строки somethingElse, сохраняя неизменность поля (т. Е. somethingElse все равно должно быть val). Я хотел бы сделать что-то похожее на это в Java:

public class MyData {

    private final int something;
    private final String somethingElse;

    public MyDate(int something, String somethingElse) {
        this.something = something;
        this.somethingElse = StringUtils.replace(somethingElse, '\n', '');

        Validate.isTrue(something > 20, "...");
        Validate.isTrue(StringUtils.isNotEmtpy(this.somethingElse), "...");
    }

    // Getters
}

Конечно, я мог бы создать нормальный класс (т.е. без класса данных) в Kotlin, но я хочу, чтобы MyData был классом данных.

Какой идиоматический способ сделать это в Котлине?

1 Ответ

0 голосов
/ 22 октября 2018

Хотя вы не можете буквально делать то, что вы хотите, вы можете подделать его.

  • Сделайте все конструкторы вашего класса данных приватными.
  • Реализация фабрик / строителей / техников на компаньоне как operator fun invoke.

Использование Companion.invoke будет - в Котлине! - выглядеть так же, как вызовы конструктора.

В вашем примере:

data class MyData private constructor(
    val something: Int, 
    val somethingElse : String
) {
    init {
        require(something > 20) { "Something must be > 20" }
        require("" != somethingElse) { "Something else cannot be blank" }
    }

    companion object {
        operator fun invoke(something: Int, somethingElse: String) : MyData =
            MyData(something, somethingElse.replace("\n", " "))
    }
}

fun main(args: Array<String>) {
    val m = MyData(77, "something\nwicked\nthis\nway\ncomes")
    println(m.somethingElse)
}

Печать:

что-то нечестивое приходит сюда

Обратите внимание на полезное предупреждение:

Конструктор закрытого класса данных предоставляется через сгенерированный метод copy.

Этот метод не может быть отменен (насколько я могу судить), поэтому вы все равно должны позаботиться. Одним из решений является скрытие фактического класса данных:

interface MyData {
    val s: Int
    val sE: String

    private data class MyDataImpl(
        override val s: Int,
        override val sE: String
    ) : MyData {
        init {
            require(s > 20) { "Something must be > 20" }
            require("" != sE) { "Something else cannot be blank" }
        }
    }

    companion object {
        operator fun invoke(s: Int, sE: String) : MyData =
                MyDataI(s, sE.replace("\n", " "))
    }
}

Теперь ваш инвариант (без разрывов строк) сохраняется, copy и другие опасные методы (если таковые имеются, я не проверял) скрыты - но, следовательно, также недоступны, потенциально удаляя некоторые удобные классы данных. .

Выберите свой яд.

...