Используя шаблон цепочки ответственности, я столкнулся с проблемой, когда ожидалось, что следующий элемент цепочки будет иметь тот же общий тип, что и первый элемент. Я знаю, почему это происходит: первый обработчик ожидает, что второй обработчик будет использовать общий тип «Apple». Я просто не знаю, как это решить.

Здесь есть ответ о том, как обрабатывать это в java, но поскольку java не имеет овеществленных типов, и весь этот подход в Kotlin должен выглядеть по-другому, верно?

На ум приходят разные варианты:

  1. Не используйте дженерики - приведет к приведению типов коллекций к определенным подтипам и не будет выглядеть чистым
  2. Попробуйте использовать овеществленные типы (как?)

Чтобы проиллюстрировать проблему, я публикую ниже демонстрационный код.


data class Apple(val name:String, val color:Int) 
data class Orange(val circumference:Double)

object Main{
    @JvmStatic
    fun main(args: Array<String>) {
        val first = FirstHandler()
        val second = SecondHandler()
        first.setNextHandler(second)  // !!! wrong type here since <Apple> is expected
        first.process()
    } 
}

abstract class ChainHandler<T>{
    protected var nextHandlerInChain:ChainHandler<T>? = null
    fun setNextHandler(handler: ChainHandler<T>): ChainHandler<T> {
        this.nextHandlerInChain = handler
        return handler
    }

    abstract fun peel(): Collection<T>
    abstract fun process():MutableMap<String,Any> }

class FirstHandler : ChainHandler<Apple>() {
    override fun peel(): Collection<Apple> {
        return Collections.emptyList<Apple>()
    }
    override fun process(): MutableMap<String, Any> {
        val peeledApples = peel()
        val map = nextHandlerInChain?.process()
        map?.put("apples",peeledApples) ?:kotlin.run {
            val map = mutableMapOf<String,Any>()
            map.put("apples",peeledApples)
        }
        return map!!
    } }

class SecondHandler : ChainHandler<Orange>() {
    override fun peel(): Collection<Orange> {
        return Collections.emptyList<Orange>()
    }
    override fun process(): MutableMap<String, Any> {
        val peeledOranges = peel()
        val map = nextHandlerInChain?.process()
        map?.put("oranges",peeledOranges) ?:kotlin.run {
            val map = mutableMapOf<String,Any>()
            map.put("oranges",peeledOranges)
        }
        return map!!
    } 
}
2
keyboardsamurai 24 Сен 2018 в 23:43

2 ответа

Лучший ответ

В Котлине есть нечто, называемое звездной проекцией, которая может вам здесь помочь. По сути, он сообщает компилятору, что вам все равно, какой тип ChainHandler вы получите. Вы можете использовать его для компиляции вашего setNextHandler, например:

abstract class ChainHandler<T>{
  // Note the star projection here
  protected var nextHandlerInChain: ChainHandler<*>? = null

  // Defining a type parameter S, so that the return type is equal to the input type.
  fun <S> setNextHandler(handler: ChainHandler<S>): ChainHandler<S> {
    this.nextHandlerInChain = handler
    return handler
  }

  ...
}

Вы можете узнать больше о проекциях звезд здесь: https://kotlinlang.org/docs/ ссылка / generics.html # звезда -проекции

Что касается определения параметра типа: параметры повторного типа работают только для встроенных функций. Не для параметров типа в классе.

5
marstran 24 Сен 2018 в 21:16

Это очень сильно зависит от специфики того, как вы хотите, чтобы обработчики взаимодействовали. Для этого кода просто измените тип следующего обработчика (как в var nextHandlerInChain, так и в fun setNextHandler на ChainHandler<*>, так как process() все равно возвращает что-то независимое от T ,

1
Alexey Romanov 24 Сен 2018 в 21:17