Home > database >  Idiomatic way to require all parameters to be set explicitly in a specific invocation? (Kotlin)
Idiomatic way to require all parameters to be set explicitly in a specific invocation? (Kotlin)

Time:10-19

Given two classes with default parameters in their constructor:

class Foo(
    val baz: Boolean = false,
)

class Bar(
    val baz: Boolean = false,
)

and an extension function to create Bars from Foos:

fun Foo.toBar() = Bar(
    baz,
)

How would I make sure not to forget to add new properties to Foo, whenever they are added to Bar (they also have default values)? The following compiles and passes all tests that I would have written at that point.

class Foo(
    val baz: Boolean = false,
)

class Bar(
    val baz: Boolean = false,
    val newProp: Boolean = false,
)

fun Foo.toBar() = Bar(
    baz,
)

Adding a test only makes sure that properties added to Foo and Bar are not removed accidentally, because I would need to remember to modify the test, just as I need to remember to modify Foo.

So, how would I make sure to notice I forgot to add something to Foo in the first place?

CodePudding user response:

So far, I've settled with a solution involving two constructors, one of them having no default parameters and the other having default parameters and a custom annotation:

@RequiresOptIn(
    message = "This constructor uses default values. "  
        "This could be dangerous because it's possible to forget to add new fields in this invocation. "  
        "Only use this if you are sure you don't need any other fields.",
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CONSTRUCTOR)
annotation class DefaultValueConstructor

This works mostly fine: As soon as I forget something in Foo.toBar(), the compiler requires me to add an @OptIn annotation. This way, I can call Bar() with default parameters whenever I'm sure I don't need any more and also make sure not to forget anything in Foo.toBar(). The only issues with it is that the constructor signatures need to be different. I would need to artificially change the signature of one of the constructors to distinguish them, which I ultimately deemed to complex. Should I resort to the factory pattern or is there another way to achieve the same goal?

CodePudding user response:

I would define a common interface between the two classes. When you add features that must be in both classes, add them to the interface, which will prevent those other classes from compiling unless you update their properties/functions.

  • Related