Модель цепи ответственности Kotlin с генериками - PullRequest
0 голосов
/ 24 сентября 2018

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

Существует ответ о том, как обработать это в Java, здесь , но поскольку в Java нет типов reified и все, что подход должен выглядеть по-другому в 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 ]

0 голосов
/ 25 сентября 2018

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

0 голосов
/ 25 сентября 2018

У Котлина есть нечто, называемое звездной проекцией, которое может вам здесь помочь.По сути, это говорит компилятору, что вам все равно, какой тип 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/reference/generics.html#star-projections

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

...