Pattern to avoid if else chain methods call using kotlin sealed class and enums - kotlin

I've a question about, how would you handle this case?
Imagine that you have to do a validation of an object and that validation should have a sort of importance, in this case we only have 3 validations, each one can result Valid or his own QualityCheck enum value.
This is the method example in kotlin and the validations
sealed class Validation {
abstract fun validate(bobject: ObjectToCheck): QualityCheck
object VeryImportantValidation : Validation() {
override fun validate(bobject: ObjectToCheck): QualityCheck =
if (isValid(bobject.valueX)) QualityCheck.Valid
else QualityCheck.VeryImportantInvalid
}
object SecondMostImportant : Validation() {
override fun validate(bobject: ObjectToCheck): QualityCheck =
if (isValid(bobject.valueNotSoImportant)) QualityCheck.Valid
else QualityCheck.SecondMostImportantInvalid
}
object NotSoImportant : Validation() {
override fun validate(bobject: ObjectToCheck): QualityCheck =
if (isValid(bobject.valueNothingImportant)) QualityCheck.Valid
else QualityCheck.NotSoImportantInvalid
}
}
fun getQualityCheck(object: ObjectToCheck): QualityCheck =
if (VeryImportantValidation.validate(object) === QualityCheck.Valid) {
if (SecondMostImportant.validate(object) === QualityCheck.Valid) {
NotSoImportant(paymentsRepository.getSystemPayments()).validate(object)
} else {
QualityCheck.SecondMostImportantInvalid
}
} else {
QualityCheck.VeryImportantInvalid
}
I think this is not scalable neither easy to read/understand or modify if we would want to add a new one.
There is any kind to do this elegant and easier to include more validations?

If you invert your Boolean conditions, you can eliminate the nesting. Then you can change it to a when statement for simplicity:
fun getQualityCheck(object: ObjectToCheck): QualityCheck = when {
VeryImportantValidation.validate(object) !== QualityCheck.Valid ->
QualityCheck.VeryImportantInvalid
SecondMostImportant.validate(object) !== QualityCheck.Valid ->
QualityCheck.SecondMostImportantInvalid
else ->
NotSoImportant(paymentsRepository.getSystemPayments()).validate(object)
}

Validation like this is a perfect candidate for the "Rules engine pattern"... mostly known as a for loop.
You just set up a List<Validation> with all of the validations you want to run and iterate over them calling the validate method. You have 2 options, collect all errors (doing a fold on the list), or stop the loop after the first error with a asSequence().map().takeWhile().
I forgot to say, you don't need to seal the Validation class. What is your intent with that?

Scalability/Extensibility would depend from situation to situation and a code cannot be open to all types of changes. One rule of thumb is to keep it as simple as possible and when a requirement is changed we ensure that the code is open to such kind of changes.
Also, I agree with #Augusto. Your use of the sealed class is not really how it is intended to be used.
Anyways let's look at how it would be easier to add a new validation, change the severity of the violation, or have several validations with the same severity.
Lets define an interface for Validations.
interface Validation {
fun validate(value: Int): Boolean
}
Now let's define a few Validations
class LimitValidation: Validation{
override fun validate(value: Int) = value < 100
}
class PositiveValidation: Validation {
override fun validate(value: Int) = value > 0
}
class EvenValidation: Validation {
override fun validate(value: Int) = value % 2 == 0
}
Let's say you have the following Violations
enum class Violation {
SEVERE,
MODERATE,
TYPICAL
}
We can make use of sealed class to define the quality.
sealed class Quality {
object High : Quality()
data class Low(val violation: Violation) : Quality()
}
We can create a class responsible for checking the Quality.
class QualityEvaluator {
private val violationMap: MutableMap<KClass<*>, Violation> = mutableMapOf()
init {
violationMap[LimitValidation::class] = Violation.SEVERE
violationMap[PositiveValidation::class] = Violation.MODERATE
violationMap[EvenValidation::class] = Violation.TYPICAL
}
fun evaluateQuality(value: Int, validations: List<Validation>) : Quality {
val sortedValidations = validations.sortedBy(::violationFor)
sortedValidations.forEach {
if(!it.validate(value)) {
return Quality.Low(violationFor(it))
}
}
return Quality.High
}
private fun <T: Validation> violationFor(validation: T): Violation {
return if (violationMap.containsKey(validation::class)) {
requireNotNull(violationMap[validation::class])
} else {
Violation.TYPICAL
}
}
}
Finally, we can use all this like so:
val validations = listOf(LimitValidation(), PositiveValidation(), EvenValidation())
when(val quality = QualityEvaluator().evaluateQuality(8, validations)) {
is Quality.High -> println("Quality is High")
is Quality.Low -> println("Quality is Low. Violation: ${quality.violation}")
}

Related

How can I know which the subclass of sealed class will return when I use Compose in Android Studio?

The Result<out R> is a sealed class which hold three subclass Success, Error and Loading.
The fun Greeting is #Composable.
By my design, I define queryList as Result class, and it is assigned as Loading first, then it will be Success or Error.
1: But the following code can't be compiled as the following error information, what's wrong with my Code?
2: Is there a better solution for my design?
Compile error
Property delegate must have a 'getValue(Nothing?, KProperty>)' method. None of the following functions are suitable.*
#Composable
fun Greeting(
name: String,
mViewMode:SoundViewModel= viewModel()
) {
Column() {
//The following code cause error.
val queryList by produceState(initialValue = Result<Flow<List<MRecord>>>.Loading ) {
value = mViewMode.listRecord()
}
when (queryList){
is Loading -> { ...}
is Error -> { ...}
is Success -> {...}
}
}
}
class SoundViewModel #Inject constructor(): ViewModel()
{
fun listRecord(): Result<Flow<List<MRecord>>>{
return aSoundMeter.listRecord()
}
}
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
object Loading : Result<Nothing>()
}
Since queryList is backed by a delegate, it can not be final.
This means in theory, each time you access it, it might hold a different value. The kotlin compiler is very pessimistic about this and assumes that between the time the is Result.Success branch of your when statement is selected and val mydata = queryList.data is executed, the value of queryList might have changed.
To solve this, you can assign the current value of queryList to a final variable and work with that one instead:
when (val currentList = queryList) {
is Result.Error -> {}
is Result.Loading -> {}
is Result.Success -> {
SomeComposable(currentList.data) //currentList is properly smart-cast to Result.Success
}
}

How to express in Kotlin "assign value exactly once on the first call"?

Looking for a natural Kotlin way to let startTime be initialized only in a particular place and exactly once.
The following naive implementation have two problems:
it is not thread safe
it does not express the fact "the variable was or will be assigned exactly once in the lifetime of an Item instance"
class Item {
var startTime: Instant?
fun start(){
if (startTime == null){
startTime = Instant.now()
}
// do stuff
}
}
I believe some kind of a delegate could be applicable here. In other words this code needs something similar to a lazy variable, but without initialization on first read, instead it happens only after explicit call of "touching" method. Maybe the Wrap calls could give an idea of possible implementation.
class Wrap<T>(
supp: () -> T
){
private var value: T? = null
private val lock = ReentrantLock()
fun get(){
return value
}
fun touch(){
lock.lock()
try{
if (value == null){
value = supp()
} else {
throw IllegalStateExecption("Duplicate init")
}
} finally{
lock.unlock()
}
}
}
How about combining AtomicReference.compareAndSet with a custom backing field?
You can use a private setter and make sure that the only place the class sets the value is from the start() method.
class Item(val value: Int) {
private val _startTime = AtomicReference(Instant.EPOCH)
var startTime: Instant?
get() = _startTime.get().takeIf { it != Instant.EPOCH }
private set(value) = check(_startTime.compareAndSet(Instant.EPOCH, value)) { "Duplicate set" }
fun start() {
startTime = Instant.now()
}
override fun toString() = "$value: $startTime"
}
fun main() = runBlocking {
val item1 = Item(1)
val item2 = Item(2)
println(Instant.now())
launch { println(item1); item1.start(); println(item1) }
launch { println(item1) }
delay(1000)
println(item2)
item2.start()
println(item2)
println(item2)
item2.start()
}
Example output:
2021-07-14T08:20:27.546821Z
1: null
1: 2021-07-14T08:20:27.607365Z
1: 2021-07-14T08:20:27.607365Z
2: null
2: 2021-07-14T08:20:28.584114Z
2: 2021-07-14T08:20:28.584114Z
Exception in thread "main" java.lang.IllegalStateException: Duplicate set
I think your Wrap class is a good starting point to implement this. I would definitely make it a property delegate and touch() could be much simplified:
fun touch() {
synchronized(this) {
check(value == null) { "Duplicate init" }
value = supp()
}
}
Then you can remove lock. But generally, this is a good approach.
If you would like to reuse lazy util from stdlib then you can do this by wrapping it with another object which does not read its value until asked:
class ManualLazy<T : Any>(private val lazy: Lazy<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return if (lazy.isInitialized()) lazy.value else null
}
fun touch() {
lazy.value
}
}
class Item {
private val _startTime = ManualLazy(lazy { Instant.now() })
val startTime: Instant? by _startTime
fun start(){
_startTime.touch()
}
}
Of course, depending on your needs you can implement it in a much different way, using a similar technique.
This may be considered exploiting or hacking lazy util. I agree and I think Wrap approach is a better one.

Suspending until StateFlow reaches one of the desired states and returning the result

Consider a sealed class State.
sealed class State {
object Unknown : State()
object Loading : State()
object Success : State()
data class Failure(val exception: Exception)
}
I have a stateflow where consumers can actively listen to the state updates.
val state:State = MutableStateFlow(State.Unknown)
Now, I also want to have a simple suspend method which waits till the state reaches either Success or Failure, so consumers who just need the result once need not be aware of the stateflow.
How to achieve this?
Although you already came up with a working solution, you might want to make use of the built-in Flow.first { ... } operator for simplicity.
suspend fun waitForResult(): State {
val resultStates = setOf(State.Success::class, State.Failure::class)
return state.first { it::class in resultStates }
}
I was able to come up with the following extension function which looks to be working fine.
suspend fun waitForResult(): State {
val resultStates = setOf(State.Success::class, State.Failure::class)
return state.waitForStates(resultStates)
}
suspend fun <T : Any> StateFlow<T>.waitForStates(states: Set<KClass<out T>>): T = coroutineScope {
var currentValue = value
// not needed for correctness, just an optimisation
if (currentValue::class in states) {
return currentValue
}
coroutineScope {
collect {
if (it::class in states) {
currentValue = it
cancel()
}
}
}
return currentValue
}

Generics in Objects

I have a question about sealed class, generics and object.
Let's say I would like to model something like 3 finite cases with a sealed class something like this:
sealed class ChangeState<S> {
fun reduceState(state: S): S
}
data class SetState<S>(val newState: S) : ChangeState<S>() {
override fun reduce(state: S): S = newState
}
object NoStateChange : ChangeState<Nothing>() { // What do I specify here for ChangeState? Nothing?
override fun reduce(state: Nothing): Nothing {
throw Exception("This should never be called")
}
}
The goal is to provide a convenient way to define NoStateChange in a generic way that it can be used as following:
fun foo(i : Int) : ChangeState<Int> {
return if (i==0)
NoStateChange // Won't compile because return type is ChangeState<Nothing> but expected ChangeState<Int>
else
SetState(i)
}
Is there a way to do that with object and Generics somehow?
As pointed out by #Tenfour04 the issue is that out is needed but reduceState() would require in as well. However, reduceState() can be refactored out of the class hierarchy and moved to an extension function like that:
sealed class ChangeState<out S>
data class SetState<S>(val newState: S) : ChangeState<S>()
object NoStateChange : ChangeState<Nothing>()
fun <S> ChangeState<S>.reduce(state: S): S {
return when (val change = this) {
is SetState -> change.newState
is NoStateChange -> state
}
}

Kotlin type inference on "supposedly" right types

I am new to Kotlin and I was playing with it. I pretty much wanted to create a pretty basic event bus. So I came up with this
interface Event
interface EventListener<E : Event> {
fun handle(event: E)
}
interface EventBus {
fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>)
}
class MyBus() : EventBus {
private val eventListeners: MutableMap<String, MutableList<EventListener<out Event>>> = mutableMapOf()
constructor(listeners: List<Pair<Class<Event>, EventListener<Event>>>) : this() {
listeners.forEach {
registerListener(it.first, it.second)
}
}
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val key = aClass.name
val listeners: MutableList<EventListener<out Event>> = eventListeners.getOrPut(key) { mutableListOf() }
listeners.add(eventListener)
}
}
val bus = MyBus(
listOf(
MyEvent::class.java to MyEventListener()
)
)
class MyEvent : Event
class AnotherEvent : Event
class MyEventListener : EventListener<MyEvent> {
override fun handle(event: MyEvent) {
}
}
what happens is that when I try to create MyBus using the constructor accepting the list of pairs, I get
Type inference failed. Expected type mismatch: inferred type is List<Pair<Class<MyEvent>,MyEventListener>> but List<Pair<Class<Event>,EventListener<Event>>> was expected
But if I change the constructor to be something like
constructor(listeners: List<Pair<Class<out Event>, EventListener<out Event>>>) : this() {
listeners.forEach {
registerListener(it.first, it.second)
}
}
adding out pretty much everywhere, then the MyBus constructor works, but the invocation to registerListener(..) breaks for the same exact reason as before. So the only way to solve this is to add "out"s also on registerListener function.
I suspect I'm doing something wrong here, but I don't know what precisely. Any help?
If you want your EventListener to be able to consume Events, then its type has to be invariant or covariant (not declared out). If it let you pass your EventListener<MyEvent> as if it were an EventListener<Event>, then your MyBus class might call listener.handle(event) on it with some Event that is not a MyEvent, such as AnotherEvent. Then you will get a ClassCastException when it tries to cast this AnotherEvent to MyEvent.
To be able to store different types of invariant EventHandlers, you will have to remove the variance restrictions by using star projection, and cast them when you retrieve them from the map. So make the map keys into class objects instead of just Strings. Since you will not have the help of the compiler when working with the star-projected types, you need to be careful that you are only adding an item to your MutableMap that is of the same type as the Class key that's associated with it. Then when you retrieve items, only cast to an invariant type.
The other part of your issue is that your constructor needs a generic type. Right now it works exclusively with Event so it can't handle subtypes of Event. Kotlin doesn't (yet?) support generic types for constructors so you have to do this with a factory function.
Here's an example of all the above.
class MyBus() : EventBus {
private val eventListeners: MutableMap<Class<*>, MutableList<EventListener<*>>> = mutableMapOf()
override fun <E : Event> registerListener(aClass: Class<E>, eventListener: EventListener<E>) {
val listeners = retrieveListeners(aClass)
listeners.add(eventListener)
}
private fun <E: Event> retrieveListeners(aClass: Class<E>): MutableList<EventListener<E>> {
#Suppress("UNCHECKED_CAST")
return eventListeners.getOrPut(aClass) { mutableListOf() } as MutableList<EventListener<E>>
}
}
// Factory function
fun <E : Event> myBusOf(listeners: List<Pair<Class<E>, EventListener<E>>>): MyBus {
return MyBus().apply {
listeners.forEach {
registerListener(it.first, it.second)
}
}
}
And you might want to change the type of the factory parameter from a <List>Pair to a vararg Pair so it's easier to use.
Here's a stripped down example to explain the variance limitation.
Your interface for an Event consumer:
interface EventListener<E : Event> {
fun handle(event: E)
}
Two implementations of Event:
class HelloEvent: Event {
fun sayHello() = println("Hello world")
}
class BoringEvent: Event {}
A class implementing the interface:
class HelloEventListener: EventListener<HelloEvent> {
override fun handle(event: HelloEvent) {
event.sayHello()
}
}
Now you have an EventListener that can handle only HelloEvents. Try to treat it like an EventListener<Event>:
val eventListener: EventListener<Event> = HelloEventListener() // COMPILE ERROR!
Imagine the compiler did not prevent you from doing this and you do this:
val eventListener: EventListener<Event> = HelloEventListener()
eventListener.handle(BoringEvent()) // CLASS CAST EXCEPTION AT RUN TIME!
If this were allowed your HelloEventListener would try to call sayHello() on the BoringEvent, which doesn't have that function, so it will crash. This is what generics are here to protect you from.
Now suppose your HelloEventListener.handle() didn't call event.sayHello(). Well, then it could have safely handled a BoringEvent. But the compiler isn't doing that level of analysis for you. It just knows what you declared, that HelloEventListener cannot handle anything except HelloEvent.