Constructing constrained/validated objects I'm trying to do some rudimentary DDD in validating my objects to ensure they are of a particular type. I.e. an Email object would always look like "my@example.com" and a NaturalNumber is always greater than 0.

However, I'm struggling to make it reusable. The closest I've got is a factory:

open class ConstrainedStringFactory&lt;T&gt;(private val creator: (String) -&gt; T, private val validator: (String) -&gt; Boolean) {
    operator fun invoke(value: String?): T? = value?.let { if (validator.test(it)) creator.apply(it) else null }
}


This can then be included as a companion object by any class wishing to verify it's type:

class Email private constructor(val email: String) {
    companion object Factory: ConstrainedStringFactory&lt;Email&gt;(::Email, { EMAIL_REGEX.matches(it) }) {
        @JvmStatic
        private val EMAIL_REGEX = "^.+@.+\\..+".toRegex()
    }
}

class NonBlankString private constructor(val value: String) {
    companion object Factory : ConstrainedStringFactory&lt;NonBlankString&gt;(::NonBlankString, String::isNotBlank)
}


This does exactly what I want: creating a new object is either valid or it's null (which can be checked safely):

val email = Email("invalid") ?: throw IlllegalArgumentException


Where this falls down is on generalisation. If I have a list of Spring Converter objects, for instance, I need to convert each individually:

Converter&lt;String, NonBlankString&gt; { value -&gt;
    NonBlankString(value) ?: throw IllegalArgumentException("The string cannot be empty")
},
Converter&lt;NonBlankString, String&gt; { nonBlank-&gt;
    nonBlank.toString()
},
Converter&lt;String, Email&gt; { value -&gt;
    Email(value) ?: throw IllegalArgumentException("The email is not valid")
},
Converter&lt;Email, String&gt; { email -&gt;
    email.toString()
}


This seems like a lot of duplication given the objects have the same signature. And it can occur in other places. Is there some way to use an interface or a parent class to make this generalise better?

Ignoring any language limitations, something similar to the following which would reduce all the boilerplate is what I'm after:

class Email private constructor(val email: String) extends ConstrainedString(email, { EMAIL_REGEX.matches(it) }) {
}

class NonBlankString private constructor(val value: String) extends ConstrainedString(value, String::isNotBlank) {
}

Converter&lt;ConstrainedString, String&gt; { value -&gt;
    value.toString()
}
Remark on you DDD

I've never thought this is possible:

val email = Email("invalid") ?: throw IlllegalArgumentException


Kotlin was created with the intention of null-safety / explicit nullability, and your factory violates it. It allowes classes to be instanceated directly as NULL - this is really weird and for people admiring and working with Kotlin it's very confusing.

Improve current solution

I don't see an improvement for your current solution. I can only recommend to make a Factory class which would genereate all the boilterplate code for all of your generates classes, including converters. But I have a suggestion...

Suggestion

I would go away from extending a companion object and define 'Nullable' Builders, which inherit from generic one:

abstract class NullableValue&lt;T, D&gt;(
    private val init: () -&gt; T,
    private val value: D,
    private val check: (D) -&gt; Boolean
) {
    fun isValid(): Boolean = check(value)

    fun getOrNull(): T? = if (isValid()) init() else null
}


Define class and the builder:

class Email(val email: String)

class NullableEmail(val email: String) : NullableValue&lt;Email, String&gt;(
    init = { Email(email) },
    value = email,
    check = { "^.+@.+\\..+".toRegex().matches(it) }
)

class NonBlankString(val string: String)

class NullableNonBlankString(val string: String) : NullableValue&lt;NonBlankString, String&gt;(
    init = { NonBlankString(string) },
    value = string,
    check = String::isNotBlank
)


And you can use them:

    NullableEmail("").getOrNull() // will be null
    NullableNonBlankString("abc").getOrNull() // will be NonBlankString.class

    Converter&lt;NonBlankString, String&gt; { value -&gt;
        value.toString()
    }




If you want more reusability, we can use strategy pattern:

interface Nullable&lt;T&gt; {

    fun isValid(): Boolean

    fun getOrNull() : T?
}

abstract class NullableValue&lt;T, D&gt; (
    private val init: () -&gt; T,
    private val value: D,
    private val check: (D) -&gt; Boolean
) : Nullable&lt;T&gt;  {
    override fun isValid(): Boolean = check(value)

    override fun getOrNull(): T? = if (isValid()) init() else null
}

class NullableEmail(val email: String) : Nullable&lt;Email&gt; by NullableByRegex&lt;Email&gt; (
    init = { Email(email) },
    value = email
)