«Двунаправленный» тип проекции / дисперсии в Котлине? - PullRequest
1 голос
/ 31 мая 2019

Допустим, у нас есть иерархия классов, подобная Pug : Dog : Mammal : Animal.

Мне нужна функция, которая принимает Bag<T> (где Bag инвариантен в T) аргумент вещей, которые являются суб-ИЛИ суперклассами Dog: fun work(things: Bag<in/out Dog) Так, что Bag<Animal>, Bag<Dog>, Bag<Pug> в порядке, но Bag<Cat> НЕ принято.

Честно говоря, я даже не уверен, как называется этот тип проекции. Есть указатели?

Ответы [ 2 ]

1 голос
/ 31 мая 2019

Если вы подумаете о том, что на самом деле означают модификаторы in и out, вы скоро поймете, что in OR out на самом деле не имеет смысла.


Давайте возьмемсначала посмотрите на out.Наличие <out T> не означает, что «Эта функция принимает T и ее подкласс в качестве параметра».На самом деле это означает «Эта функция не будет делать ничего, что не может сделать параметр класса T или любой из его подклассов». Этот модификатор накладывает ограничения на то, что вы можете сделать с этим параметром.

Проверьте этот код:

fun doThingsWithBag(bag: Bag<out Dog>) {
    bag.getAny().walk() // allowed

    bag.put(Dog()) // not allowed
}

Вы не можете класть в эту сумку ни одной собаки,потому что эта сумка на самом деле может быть типа Bag<Pug>, а Bag<Pug> не будет принимать случайные Dog.


То же самое относится и к in.<in T> не означает, что «Эта функция принимает T и ее супер».Это больше похоже на «Эта функция не будет делать ничего T или любой из ее суперклассов не может делать».

fun doThingsWithBag(bag: Bag<in Dog>) {
    bag.put(Dog()) // allowed

    bag.getAny().walk() // not allowed
}

Теперь вы можете положить собаку в сумку, но вы ничего не можете поделать с собаками внутри сумки, потому что сумка на самом деле может быть Bag<Animal>, и нет никакой гарантии, что«вещь», которую вы достаете из сумки, - это Dog.

Итак, как вы можете видеть, существует четкое различие между тем, что могут делать in и out, и поэтому они не могут сосуществовать.


Даже интерфейс, предложенный в другом ответе, не сильно вам поможет, потому что вашему классу Animal также потребуется реализовать тот же интерфейс, что означает, что Cat такжеиметь доступ к этому интерфейсу.Единственный способ обойти это - заставить work метод принять Bag<Any> и вручную проверить его универсальный класс.

Обновление:

Что вы пытаетесьсделать невозможно в ООП.То, что вы ищете, в основном выглядит примерно так:

update(Animal()) // allowed
update(Dog())    // allowed
update(Pug())    // allowed
update(Cat())    // not allowed (compile time error)

Однако компилятор не может помешать вам сделать это:

val animal: Animal = Cat()
update(animal)  // ???

Вы можете сделать что-то подобноетолько во время выполнения, проверяя тип ввода вручную.

0 голосов
/ 31 мая 2019

Честно говоря, я даже не уверен, как называется этот тип проекции. Есть указатели?

Мне кажется, , что вам нужно, это использовать Интерфейсы , создав Interface, где вы определяете свой work метод. Это хорошо, поскольку соответствует принципу Состав по наследованию .

После этого попросите классы, которые вы хотите использовать, реализовать интерфейс (скажем, class Dog : Mammal(), WorkInterface), и не реализуйте его в тех классах, которые вам не нужны / не нужны work.

Кроме того, пусть они переопределяют метод work. Таким образом, вы сможете вызывать метод work для экземпляров ваших объектов, когда захотите. Например, ваш код должен выглядеть примерно так:

Интерфейс WorkInterface :

interface WorkInterface{
    fun work(foo:String)    //add the parameters you need
}

Использование интерфейса в таком классе, как Dog:

//implementing and extending are done the same way in Kotlin, after the colon
class Dog : Mammal(), WorkInterface{
    //define your attributes, methods etc..

    //override all the methods defined in the interface
    override fun work(foo: String){
        //implement the specific code you want this to do
    }
}

После чего вы можете использовать на своем Dog экземпляре, как myDog.work("whatever").

...