Creating chainable methods in an interface

I have some default methods in an interface, like this:

	/** Changes this component's color to the supplied [color] */
	fun color(color: ChatColor?) = this.apply { component.color = color }

The problem is that, in subclasses, this method returns an instance of the interface so you can no longer chain anything that the interface doesn’t have!

I really want to do something like this for all those functions, and have Kotlin return this automatically and recognize it as the proper type:

	/** Changes this component's color to the supplied [color] */
	chain fun color(color: ChatColor?) { component.color = color }

I’m aware of apply, but look here. This is using the class my helper interface is meant to wrap:

Long code snippet
			TextComponent(token.value).apply {
				hoverEvent = HoverEvent(
					HoverEvent.Action.SHOW_TEXT,
					arrayOf(
						TextComponent("This is a ").apply {
							color = ChatColor.GOLD
							addExtra(TextComponent("placeholder").apply { isBold = true })
							addExtra(".\n")
						},
						TextComponent("Placeholders interact with ").apply {
							color = ChatColor.GRAY
							addExtra(TextComponent("PlaceholderAPI").apply { isBold = true })
							addExtra(" in order to show dynamic text.\n")
						},
						TextComponent("If you don't have PlaceholderAPI, this won't work!").apply {
							color = ChatColor.GRAY
						}
					)
				)
			}

As opposed to:

Another long code snippet
			LTextComponent(token.value).hover(
				HoverEvent(
					HoverEvent.Action.SHOW_TEXT,
					arrayOf(
						!LTextComponent("This is a ")
							.color(ChatColor.GOLD)
							.extra(
								LTextComponent("placeholder")
									.bold(true)
							)
							.extra(LTextComponent(".\n")),
						!LTextComponent("Placeholders interact with ")
							.color(ChatColor.GRAY)
							.extra(
								LTextComponent("PlaceholderAPI")
									.bold(true)
							)
							.extra(LTextComponent(".\n")),
						!LTextComponent("If you don't have PlaceholderAPI, this won't work!")
							.color(ChatColor.GRAY)
					)
				)
			)

As is, that code works fine. However, I’d like to add an overload for extra(text: String) in the LTextComponent class, but not in the interface (which is value-agnostic). So I have this in the class:

	/** Adds [extra] to this component as a child LTextComponent */
	fun extra(text: String) = extra(LTextComponent(text))

But the problem is, all those chainable methods return the interface, so you have to call all the LTextComponent methods first, or cast, or something stupid like that!

Is there any way to create chainable methods like this without redefining them all manually in the conforming class? So, for example, a call to extra or bold return an instance of the class, rather than the interface it’s implementing.

In general, the problem you are talking about is solved by so-called self-types, which are not present in kotlin at the moment.

In your particular case, you do not need it since you have statically dispatched extension:

interface YourInterface

fun <T: YourInterface> T.color(color: ChatColor?) = apply { component.color = color }

It will return the caller object, not the interface. In Kotlin you do not need default methods in interfaces if you do not expect to override them. Use extensions instead.

2 Likes

Hi @LoganDark,
do you considered a DSL to solve your specific issue?

Thank you! Somehow I didn’t think of that… I’m 99% sure that’s going to work, and even if it doesn’t I’ll probably be able to figure it out.

Sorry for the slow response time, got the notification but then forgot to reply >.<

I wish this forum had “mark as solution” enabled :stuck_out_tongue: