首页 > 解决方案 > How to enforce relationship between Kotlin classes

问题描述

Im new to Kotlin and investigating what is/isnt possible

I have a use case as follows:-

As a technical exercise I am attempting to model remote API requests and responses, and enforce relationships between them

My goal is to be able to declare the relationship between Requests and Responses in a clear and succinct way at the top of a Class. This will 1). document the API calls made by this Class, 2). Enforce the relationship so that Request1 can only produce Response1

Pseudo code:-

Requests {
  Request1 -> Response1   
  Request2 -> Response2
  ...   
  RequestN -> ResponseN
}

I have defined two interfaces Request & Response and employ them as follows:-

interface Request {
    fun <T> response(data : T): Lazy<Response>
}

interface Response

data class Request1(val request: String) : Request {
    data class Response1(val output: String) : Response
    override fun <T> response(data: T): Lazy<Response> {
        return lazy { Response1(data as String) }
    }
}

data class Request2(val request: Long) : Request {
    data class Response2(val output: Double) : Response
    override fun <T> response(data: T): Lazy<Response> {
        return lazy { Response2(data as Double) }
    }
}

I have a Controller class that makes the API calls as follows:-

class Controller {
    fun call(request: Request): Lazy<Response> {
        return when (request) {
            is Request1 -> request.response("Testing Data")
            is Request2 -> request.response(Math.PI)
            else -> TODO()
        }
    }
}

Using the above data classes I can enforce that Request1 is linked to only Response1 and also specify the response data type wrapped by each Response.

Although the above classes provide the functionality and adhere to these rules, they are verbose.

Is there a more succinct approach I could employ to obtain the desired result.

The reason I require this is I am looking for "Self Documenting" code, where a developer can view the definition of Request/Response pairs and association rules and clearly see what is intended.

For example: A developer looking at the final Request definitions can clearly see that Response1 with be generated by Request1. I also want to enforce that Response1 can only ever be produced from Request1.

My example above is simplified, as in "The Real World" the data wrapped by each Response will be sourced from the actual API request call, I have illustrated with "Hard Coded".

I would much rather define Request1 and Response1 on a single line if possible.

UPDATE

I have refactored my original classes as follows:-

interface Request<ResponseData> {
    fun response(data: ResponseData): Lazy<Response>
}

interface Response

sealed class Requests<T> : Request<T> {
    data class Request1(val request: String) : Requests<String>() {
        inner class Response1(val output: String) : Response

        override fun  response(data: String): Lazy<Response> {
            return lazy { Response1(data) }
        }
    }

    data class Request2(val request: Long) : Requests<Double>() {
        inner class Response2(val output: Double) : Response

        override fun response(data: Double): Lazy<Response> {
            return lazy { Response2(data) }
        }
    }
}


class Controller {
    fun <T> call(request: Request<T>): Lazy<Response> {
        return when (request) {
            is Requests.Request1 -> request.response("Testing Data")
            is Requests.Request2 -> request.response(Math.PI)
            else -> TODO()
        }
    }
}

While this version of my code has many benefits from the original, one feature I am still not happy with is that each Request/Response declaration is still quite verbose, e.g. it requires 5 lines of code. Is there an approach I can employ to make each Request/Response pair declaration more succinct?, e.g. take up fewer lines of code.

UPDATE II

Im attempting to refactor my sealed class above so that the overridden function response is defined in the outer sealed class.

interface Request<ResponseData> {
    fun response(data: ResponseData): Lazy<Response>
}

interface Response

sealed class Requests<T> : Request<T> {
    data class Request1(val request: String) : Requests<String>() {
        inner class Response1(val output: String) : Response
    }

    data class Request2(val request: Long) : Requests<Double>() {
        inner class Response2(val output: Double) : Response
    }

    override fun response(data: T): Lazy<Response> {
       return lazy { // What implementation goes here??? // }
    }
}

Is this approach possible? How do I refer to the individual concrete ResponseN classes in the outer sealed class?

标签: kotlin

解决方案


Maybe your example is simplified from what you're actually doing, but I don't see the purpose of the Response interface, or the need for separate Request implementations to achieve what your code does:

data class Request<T>(val request: String, val responseType: KClass<out T>) {
    fun response(data : T) = lazy { data }
}

class Controller {
    fun <T: Any> call(request: Request<T>): Lazy<T> {
        @Suppress("UNCHECKED_CAST")
        return when (request.responseType) {
            String::class -> request.response("Testing Data" as T)
            Double::class -> request.response(Math.PI as T)
            else -> TODO()
        }
    }
}

It's kind of an odd use of Lazy though, since you are wrapping a pre-computed value.


推荐阅读