3

I have a class that registers itself as an event handler, with an event service:

interface CommunicationService {
    fun sendActivationMessage(toEmail: String)
}

abstract class EventCommunicationService : CommunicationService, AbstractEventHandler {
    constructor(eventService: EventService) {
        eventService.registerListener(this)
    }

    override fun onEvent(event: Event) {
        if (event.type == EventType.USER_CREATED) {
            sendActivationMessage(event.userEmail)        
        }
    }
}

The idea being there can be an EmailCommunicationService, or a mocked testing version, etc. which don't all need to register themselves as listeners for when a user is created.

However Kotlin complains that I'm:

Leaking 'this' in constructor of non-final class EventCommunicationService

Which, well, I am. I could easily ignore the warning - but is there a better approach?

I've tried using an init { } block instead of a constructor, but the warning is the same.

I basically want a "post-construct" callback or similar that can be used to let this service register itself with the EventService provided in the constructor since that's the point of this intermediate type.

I understand why this is a problem - but I'm not sure how to reason my way to fixing it.

Craig Otis
  • 31,257
  • 32
  • 136
  • 234

1 Answers1

2

init blocks are really part of the constructor (in JVM terms), so that wouldn't help with the problem. It is very much not safe to ignore in general: see Leaking this in constructor warning for reasons (just ignore the accepted answer, its comments contain the real meat and so does Ishtar's answer).

One option (assumes that all subclasses have no-argument constructors, though it could be extended):

abstract class <T : EventCommunicationService> EventCommunicationServiceCompanion(private val creator: () -> T) {
    operator fun invoke(eventService: EventService): T {
        val obj = creator()
        eventService.registerListener(obj)
        return obj
    }
}

// a subclass of EventCommunicationService:
class MyService private constructor () : EventCommunicationService {
    companion object : EventCommunicationServiceCompanion<MyService>(MyService::new)
}

To create a MyService, you still call MyService(eventService), but this is actually the companion object's invoke method and not the constructor.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487