How can I create a component-based message handling system with traits? - oop

Here's a short example of what I want to do:
abstract class Message()
case class FooMessage() extends Message
case class BarMessage() extends Message
//... other messages ...
trait Component
{
def handleMessage(msg: Message):Unit
}
trait ComponentType1 extends Component
{
abstract override def handleMessage(msg: FooMessage) = {
//handle foo, pass it up the chain
super.handleMessage(msg)
}
abstract override def handleMessage(msg: BarMessage) = {
//handle bar, pass it up the chain
super.handleMessage(msg)
}
}
//handles some other messages, also might handle the same messages as ComponentType1
trait ComponentType2 extends Component { .. }
Then, these ComponentTypes are mixed in to a class to form an object that is completely composed of modular components.
I have a bunch of different Messages and a bunch of different Components.
Not all components handle all message types.
Multiple components can handle the same message type.
The message cascades up through the components, even if it's handled by another component.
A Component can handle more than one message type.
The problem is since handleMessage is defined in Component as accepting a Message, when I try to specialize the msg parameter it doesn't count as an override.
I know one possible solution to this is to declare a big handleMessage method with a big match statement, but I'd like to define a method per message if possible.

Here's a partial function based solution. Tip to CheatEx for the idea.
trait Message
class FooMessage extends Message
class BarMessage extends Message
abstract class Component {
type CPF = PartialFunction[Message, Unit]
var pf: CPF = { case _ => }
def handleMessage(msg: Message) = pf(msg)
}
trait ComponentType1 extends Component {
val ct1pf: CPF = {
case msg: FooMessage => println("foo1")
case msg: BarMessage => println("bar1")
}
pf = ct1pf orElse pf
}
trait ComponentType2 extends Component {
val parentPf = pf
val ct2pf: CPF = {
case msg:
BarMessage => println("bar2")
parentPf(msg) // cascade
}
pf = ct2pf orElse pf
}
object component1and2 extends ComponentType1 with ComponentType2
component1and2.handleMessage(new FooMessage)
component1and2.handleMessage(new BarMessage)
prints
foo1
bar2
bar1

trait Message
class FooMessage extends Message
class BarMessage extends Message
trait Component {
def handleMessage(msg: Message) {}
def handleMessage(msg: FooMessage) {}
def handleMessage(msg: BarMessage) {}
}
trait ComponentType1 extends Component {
override def handleMessage(msg: FooMessage) = println("foo1")
override def handleMessage(msg: BarMessage) = println("bar1")
}
trait ComponentType2 extends Component {
override def handleMessage(msg: BarMessage) = println("bar2")
}
object component1and2 extends ComponentType1 with ComponentType2
component1and2.handleMessage(new FooMessage)
component1and2.handleMessage(new BarMessage)
prints
foo1
bar2
If you had a list somewhere of all the components in the system you could do
componentList.foreach(c => c.handleMessage(msg))
It would just no-op in Component for the components that didn't handle msg.

For completeness:
trait Message
class FooMessage extends Message
class BarMessage extends Message
trait Component {
def handleMessage(msg: Message) {}
}
trait ComponentType1 extends Component {
def handleMessage(msg: Message) {
msg match {
case m: FooMessage => println("foo1")
case m: BarMessage => println("bar1")
case _ => super.handleMessage(msg)
}
}
}
trait ComponentType2 extends Component {
override def handleMessage(msg: Message) {
msg match {
case m: BarMessage =>
println("bar2")
super.handleMessage(m) // cascade
case _ => super.handleMessage(msg)
}
}
}
object component1and2 extends ComponentType1 with ComponentType2
component1and2.handleMessage(new FooMessage)
component1and2.handleMessage(new BarMessage)
prints
foo
bar2
bar1

Related

How to set multiple params in this type "Class...parameterTypes" when building Micronaut programmatically routes -io.micronaut.web.router.RouteBuilder

I'm trying to build routes for my service with Micronaut. Following this tutorial the example works fine.
This is my controller:
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.PathVariable
#Controller
class DemoController {
#Get
fun issue(
#PathVariable a: String): String {
return "Issue # $a"
}
}
And this is my route class:
import io.micronaut.context.ExecutionHandleLocator
import io.micronaut.web.router.DefaultRouteBuilder
import io.micronaut.web.router.RouteBuilder
import jakarta.inject.Inject
import jakarta.inject.Singleton
#Singleton
class MyRoutes(executionHandleLocator: ExecutionHandleLocator,
uriNamingStrategy: RouteBuilder.UriNamingStrategy) :
DefaultRouteBuilder(executionHandleLocator, uriNamingStrategy) {
#Inject
fun issuesRoutes(demoController: DemoController) {
GET("/issues/show/{number}", demoController, "issue", String::class.java)
}
}
Everything working fine so far.
The problem is that I have more than one parameter in the endpoint. For example:
#Controller
class DemoController {
#Get
fun issue(
#PathVariable a: String,
#PathVariable b: String
): String {
return "Issue # $a"
}
}
In MyRoutes class, on the function issuesRoutes, I need to set the parameterTypes for 2 params now, and I don't know how should I do it.
The documentation of RouteBuilder says as follow:
Route the specified URI template to the specified target.
The number of variables in the template should match the number of method arguments
Params:
uri – The URI
target – The target
method – The method
**parameterTypes – The parameter types for the target method**
Returns:The route
#Override
public UriRoute GET(String uri, Object target, String method, Class... parameterTypes) {
return buildRoute(HttpMethod.GET, uri, target.getClass(), method, parameterTypes);
}
How could I tell the method the types of my two string params (#PathVariables) in this kind of param the method is expecting (Class... parameterTypes).
The error I get with this configuration is:
Caused by: io.micronaut.web.router.exceptions.RoutingException: No such route: com.example.rest.DemoController.issue
Given a controller:
#Controller
class DemoController {
#Get
fun issue(#PathVariable a: String, #PathVariable b: String) = "Issue # a=$a b=$b"
}
The route would be:
#Singleton
class MyRoutes(executionHandleLocator: ExecutionHandleLocator,
uriNamingStrategy: RouteBuilder.UriNamingStrategy) :
DefaultRouteBuilder(executionHandleLocator, uriNamingStrategy) {
#Inject
fun issuesRoutes(demoController: DemoController) {
GET("/issues/show/{a}/{b}", // uri
demoController, // target
"issue", // method
String::class.java, String::class.java) //vararg parameterTypes:Class
}
}

How do I create an instance of a Class passed as parameter to a function?

I'm building a library in Kotlin and here's my usecase
I have a base class
abstract class Component(...) {
// ... class body
}
I want the users of my library to define their own sub-classes like say:
class MyComponent() : Component() {
// .. class body
}
How can I write a helper function that takes in this derived class as a param and create an instance out of it. Something like:
fun helper(component: Class, props: HashMap<String, String>) : Component {
// somehow create a new instance of Class and return it?
}
Thanks!
You can have users pass a constructor reference:
fun helper(componentConstructor: ()->Component, props: Map<String, String>) : Component {
val component = componentConstructor()
// set it up and return it.
}
// usage:
val component = helper(::MyComponent, emptyMap())
Better for props not to require a specific type of map since it doesn’t matter here. Needless burden for users of your library.
abstract class Component(val prop1: String, val prop2: String) {
// ... class body
}
class MyComponent(prop1: String, prop2: String) : Component (prop1, prop2) {
// ... class body
}
fun helper(component: Class<MyComponent>, props: Map<String, String>): Component {
val constructor = component.constructors.first { it.parameterCount == props.size }
val arguments = props.values.toTypedArray()
return constructor.newInstance(*arguments) as Component
}
val instance = helper(MyComponent::class.java, mapOf("prop1" to "value1", "prop2" to "value2"))
println(instance.prop1 + ", " + instance.prop2) // Prints: value1, value2

replace enums with multiple extends sealed classes

Currently I have three enum classes that represents states in my state machine and one to display operations
interface State
enum class OperationState : State {
InProgress,
Finished,
Error
}
enum class FirstState : State {
//some states
}
enum class NextState: State {
//some states
}
enum class LastState: State {
//some states
}
In my service I have:
when (state) {
is FirstState -> {
//do something
changeFirstStateOperationState(state)
}
is NextState -> {
//do something
changeNextStateOperationState(state)
}
is LastState -> {
//do something
changeLastStateOperationState(state)
}
}
private fun changeFirstStateOperationState(state: FirstState){
when(state){
FirstState.A -> OperationState.Error
listOf(FirstState.B, FirstState.C) -> OperationState.InProgress
FirstState.D -> OperationState.Finished
}
}
I would like change my current implmenetation to sealed classes.
I tried something like:
sealed class State {
sealed class OperationState : State() {
sealed class FirstState : OperationState() {
object A: FirstState()
object B: FirstState()
object C: FirstState()
object D: FirstState()
}
sealed class NextState:OperationState(){ ... }
sealed class LastState:OperationState(){ ... }
}
}
but it doesn't work...
Is there any way, using the sealed class, to know what is current OperationStatus without mapping State to it?
This works for me
sealed class State {
sealed class OperationState : State() {
sealed class FirstState : OperationState() {
object A: FirstState()
object B: FirstState()
object C: FirstState()
object D: FirstState()
}
sealed class NextState:OperationState(){ }
sealed class LastState:OperationState(){ }
}
}
fun main() {
checkState(State.OperationState.FirstState.A)
}
fun checkState(state: State) {
when(state) {
is State.OperationState.FirstState.A -> "it's A"
is State.OperationState.FirstState -> "it's another FirstState"
is State.OperationState.NextState -> "it's a NextState"
is State.OperationState.LastState -> "LastState"
}.run(::println)
}
Just note that you have to specify the entire type name (State.OperationState...) unless you do an import (probably recommended if you're nesting this much), and also you have to use is in the when matcher, because you're comparing types. You don't need it for the first one (FirstState.A) since you're comparing to a specific singleton object, so it's up to you how you want it to look

Calling an overloaded method with the base class parameter type

Is it possible in Kotlin to call an overloaded method using the base class type as a parameter? This is best illustrated via an example
Base Sealed Class + Derived Classes
sealed class Event {
abstract val eventId: String
}
data class FirstEvent(
override val eventId: String
val first: String
) : Event()
data class SecondEvent(
override val eventId: String
val second: String
) : Event()
Utility Class having an overloaded method for each of the derived classes
class UtilityClass {
fun handle(event: FirstEvent) {
....
}
fun handle(event: SecondEvent) {
....
}
}
Is it possible to call methods of the utility class in such a way utility.handle(FirstEvent("id", "first) as Event) doing so is giving me the following exception
None of the following functions can be called with the arguments supplied.
you can do something like this
fun handleEvent(event: Event) {
when (event) {
is FirstEvent -> {
// event is automatically casted as FirstEvent
event.first
}
is SecondEvent -> ...
}
}

Testing with spek and sharing some base test-cases for base-classes

I'm using Spek as test framework, and have troubles when sharing some test-steps for base classes.
I have an abstract base class and two derived classes.
abstract class Base {
abstract fun send()
}
class Foo : Base() {
override fun send() {}
fun anotherFunction() { }
}
class Bar : Base() {
override fun send() {}
fun differentFunction() { }
}
Now my question is: How can I create Speks for those classed, but only define the test for send() once in a base spek?
My first approach was to use SubjectSpek
class BaseSpek : SubjectSpek<Base>({
subject {
// ??? Can't instantiate Base because it is abstract
}
it("test the base") { ... }
})
class FooSpek : SubjectSpek<Foo>({
itBehavesLike(BaseSpek)
it("test anotherFunction") { ... }
})
My second approach was to use inheritance:
abstract class BaseSpek(base: Base) : Spek({
it("test the base") { ... }
})
abstract class FooSpek() : BaseSpek(???)
It seems that none of my approaches works. Any suggestions how to solve this? Should I bring this to the attention of the Spek-Author for possible changes in future releases of Spek?
SubjectSpek is the correct approach.
abstract class BaseSpec: SubjectSpek<Base>({
it("test base") { ... }
})
object FooSpec: BaseSpec<Foo>({
subject { ... }
// ugly for now, until Spek supports #Ignore
itBehavesLike(object: BaseSpec() {})
it("test another") { ... }
})