kevinmm
kevinmm

Reputation: 3376

Refer to super in default values of constructor parameters when implementing an interface

Assume I have an interface that provides many immutable properties with default getters, as such

interface Repository {
    val name : String
    val pattern : String
        get() = "foo/bar/baz"
    var url : String
        get() = "http://mycompanyrepo.com/$name"

    fun add() {
        TODO("do something interesting here")
    } 
}

Now, there are several concrete implementations that just use most of the defaults. However, I also want to provide a ConfigurableRepository that allows for more flexibility in other projects that want to configure this at runtime, based on other user parameters.

How can I create a primary constructor that has optional parameters?

I was hoping something along the lines of:

class ConfigurableRepo(var name, var pattern, var url) {
   ...
}

Edited for clarification

The intent here is to use primary constructor behavior, where the named parameters are optional. More specifically, name, pattern, and url are all optional to anyone calling the constructor, and it will default to the interface's getters. But, I'm obviously trying to force the Kotlin interface to fit this model and it is probably a noob Kotlin user mistake, as I ultimately have no access to super here.

From the class docs

In fact, for declaring properties and initializing them from the primary constructor, Kotlin has a concise syntax:

class Person(val firstName: String, val lastName: String, var age: Int) {      
    // ...
}

I seem to be getting errors that this hides members of the supertype and requires the override modifier. If I try to override the supertype and then provide a default, it complains that supertypes aren't accessible in this context.

Can this only be done in a secondary constructor with mutable values?

I'm also open to suggestions/feedback on better ways to provide an interface that supports configuration over convention. The most important aspect being that the interface always provide defaults for a convention. Or, more simply put:

Is there a better way?

Upvotes: 2

Views: 1121

Answers (4)

Robert Jack Will
Robert Jack Will

Reputation: 11541

Since I now figured out how to do it with the vars, here an answer for the exact interface from your question.

class ConfigurableRepo(
        private var _name: String,
        private var _pattern: String,
        private var _url: String) : Repository 
{
    override val name get () = _name

    override val pattern get () = _pattern

    override var url
        get () = _url
        set (u: String) { _url = u }
}

Upvotes: 1

Robert Jack Will
Robert Jack Will

Reputation: 11541

If your intention is to have default values for the parameters of the default constructor like this (pseudocode, which does not compile):

class ConfigurableRepo(
    override var name, 
    override var pattern = super.pattern, 
    override var url = super.url
) {
    ...
}

Then the way to go would be the "null as default pattern" which one can always use when the desired default value can't be expressed in the method declaration. In your case, it could work out like this:

class ConfigurableRepo (
        override val name: String,
        _url: String? = null,
        _pattern: String? = null
): Repository {
    override val pattern = _pattern ?: super.pattern
    override var url = _url ?: super.url

    // ... more code ...
}

As you can see, the trick works with val as well as var properties and fields. Property name which does not have a default value in the interface is implemented with straight-forward field in this class.

Upvotes: 0

Willi Mentzel
Willi Mentzel

Reputation: 29844

Use an abstract class like this:

abstract class Repository {
    open val name = "default"
    open val pattern = "default"
    open val url = "default"
}

// example: only override name
class RepoWithDifferentName(
    override val name: String
): Repository() {
   // ...
}

Upvotes: 1

NSimon
NSimon

Reputation: 5287

Please have a look at https://kotlinlang.org/docs/reference/visibility-modifiers.html

Especially the 2nd paragraph around Classes and interfaces.

From these examples, a suggestion would be, if it's possible in your case, to use a base class instead of an interface. This would look something like this :

   open class DefaultRepo {
        protected open val name = "foo/bar/baz"
        protected open val pattern = "foo/bar/baz"
        protected open val url = "http://mycompanyrepo.com/$name"

        open fun add() {
            TODO("do something interesting here")
        }
    }

    class RenamedRepo(newName: String): DefaultRepo() {
        override val name = newName
    }

    class ConfigurableRepo(n: String, p: String, u: String): DefaultRepo() {
        override val name = n
        override val pattern = p
        override val url = u

        override fun add() {

        }
    }

Upvotes: 1

Related Questions