dimanche 28 juin 2020

Avoid casting using polymorphism (or design patterns)

I have the following classes:

interface Event

interface NotificationEvent : Event

data class InviteEvent(val userId: Long, val inviteeId: Long) : NotificationEvent

Event represents a generic event
NotificationEvent represents an event that trigger a notification
InviteEvent represents an implementation of NotificationEvent

I would write a code that reacts to notification events, writing handlers for every type of event.
Accordingly to "Open-close" principle, I'd like to avoid to edit some existing classes to handle new types of event (i.e. avoid the hell switch case). The idea that I came up with was creating the following classes:

abstract class NotificationEventHandler<T : NotificationEvent> {
    fun handle(notificationEvent: NotificationEvent) {
        @Suppress("UNCHECKED_CAST")
        if (isSupported(notificationEvent)) {
            handleInternal(notificationEvent as T)
        }
    }

    protected abstract fun isSupported(notificationEvent: NotificationEvent): Boolean
    protected abstract fun handleInternal(notificationEvent: T)
}

@Component
class InviteEventHandler : NotificationEventHandler<InviteEvent>() {

    override fun isSupported(notificationEvent: NotificationEvent) =
        notificationEvent is InviteEvent

    override fun handleInternal(notificationEvent: InviteEvent) {
        // Logic here
    }

}

The idea is that, in my service I can autowire all my NotificationHandler classes, call handle on each one, and the internal logic will call handleInternal if necessary.

@Service
@Transactional
class NotificationServiceImpl(
    val notificationEventHandlers: List<NotificationEventHandler<*>>
) : NotificationService {
    override fun onNotificationEvent(notificationEvent: NotificationEvent) {
        notificationEventHandlers.forEach {
            it.handle(notificationEvent)
        }
    }

I really do not like this implementation... when I see casting in my code, normally I'm doing something wrong (i.e. missing some design pattern or ignoring polymorphism powers). Moreover I'm invoking handle on every handler instead of calling it only on supported ones.

Do you have some idea how to implement this without casting?

Thank you so much.
Francesco

Aucun commentaire:

Enregistrer un commentaire