Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

التغاير مع in وout

جزء من قسم البرمجة كائنية التوجه في رحلة Kotlin على Coddy. الدرس 40 من 57.

يمكن تمرير List<String> حيث يُتوقَّع List<Any>، لأن List لا تفعل سوى إخراج العناصر. أما MutableList<String> فلا يمكن ذلك: إذ يمكن للكود الذي يراها على أنها MutableList<Any> أن يضيف قيمة Int إلى قائمة من السلاسل النصية:

fun describeAll(items: List<Any>) = items.joinToString { it::class.simpleName ?: "?" }
fun addNumber(items: MutableList<Any>) { items.add(42) }

داخل main:

val words = listOf("a", "b")
println(describeAll(words))
val mixed = mutableListOf<Any>("x")
addNumber(mixed)
println(mixed)
// addNumber(mutableListOf("a"))   // خطأ: MutableList<String> ليست MutableList<Any>

الناتج:

String, String
[x, 42]

out T يجعل الفئة العامة متغايرة التباين: فهي تنتج قيم T فقط، بوصفها أنواعًا مُعادة، ولا تستقبلها مطلقًا. لذلك تُعدّ Producer<Cat> من نوع Producer<Animal>:

open class Animal(val name: String)
class Cat(name: String) : Animal(name)

class Producer<out T>(private val item: T) {
    fun get(): T = item
}

fun greet(p: Producer<Animal>) = "hello ${p.get().name}"

داخل main:

val cats: Producer<Cat> = Producer(Cat("Tom"))
println(greet(cats))

الناتج:

hello Tom

in T يجعلها متناقضة: فهي تستهلك قيم T فقط، باعتبارها معاملات. يمكن لمستهلك يتعامل مع أي Animal أن يحل محل مستهلك لـ Cat:

open class Animal(val name: String)
class Cat(name: String) : Animal(name)

interface Feeder<in T> {
    fun feed(animal: T): String
}
class AnimalFeeder : Feeder<Animal> {
    override fun feed(animal: Animal) = "fed ${animal.name}"
}

fun feedCats(cats: List<Cat>, feeder: Feeder<Cat>) = cats.map { feeder.feed(it) }

داخل main:

println(feedCats(listOf(Cat("Tom"), Cat("Kit")), AnimalFeeder()))

الناتج:

[fed Tom, fed Kit]

يفرض المصرّف الاتجاه: لا يمكن أن تكون معلمة النوع out نوعًا لمعلمة طريقة، ولا يمكن إرجاع معلمة النوع in. القاعدة العامة هي: المنتج out، والمستهلك in. تفعل المكتبة القياسية الشيء نفسه: List<out E>، Comparator<in T>. من دون أي من الكلمتين الرئيسيتين، تكون معلمة النوع غير متغايرة، كما في MutableList<E>، التي تقرأ وتكتب معًا:

class Producer<out T>(private val item: T) {
    fun get(): T = item                  // جيد: T يخرج
    // fun set(x: T) { }                 // خطأ: T مُعلن out
}
interface Feeder<in T> {
    fun feed(animal: T): String          // جيد: T يدخل
    // fun last(): T                     // خطأ: T مُعلن in
}

يمكن أيضًا كتابة التغاير حيث يُستخدم نوع ما. تقبل MutableList<in Int> أي قائمة قابلة للتغيير يمكنها استيعاب قيم Int، مثل MutableList<Number>، بينما تسمح MutableList<out Number> بالقراءة فقط:

fun fillWith(target: MutableList<in Int>, n: Int) {
    for (i in 1..n) target.add(i)
}
fun total(source: MutableList<out Number>) = source.sumOf { it.toDouble() }

داخل main:

val numbers = mutableListOf<Number>(0.5)
fillWith(numbers, 3)
println(numbers)
println(total(mutableListOf(1, 2, 3)))

الناتج:

[0.5, 1, 2, 3]
6.0
challenge icon

التحدي

سهل

تنقل واجهتان العناصر: تنتج Source<out T> العناصر باستخدام next() (وتُرجع null عندما تكون فارغة)، بينما تستهلكها Sink<in T> باستخدام accept(item). وهما موفرتان لك. اكتب ListSource<T>(items)، الذي يُرجع عناصر القائمة واحدًا تلو الآخر ثم null؛ وCollector<T>، الذي يحتفظ بالعناصر المقبولة في قائمة items للقراءة فقط؛ وCounter، وهو Sink<Any> يعدّ كل ما يقبله. ثم اكتب pipe(from, vararg to)، الذي ينقل كل عنصر من المصدر إلى كل مصرف ويُرجع عدد العناصر التي نقلها.

يربط الكود الموفَّر سطرًا من الكلمات بـ Collector<String> وسطرًا من الأرقام بـ Collector<Number>، وفي المرتين أيضًا بـ Counter واحد. ينجح ذلك بسبب out وin: إن ListSource<Int> هو Source<Number>، وSink<Any> هو Sink<String>. ويطبع عدد العناصر، وكلتا المجموعتين، وcounted: 7 وafter the end: null.

ضع الكود الخاص بك في ListSource.kt وCollector.kt وCounter.kt وPipe.kt وSource.kt وSink.kt. يحتوي Main.kt على كود الإدخال/الإخراج الموفَّر ولا يمكن تعديله.

جرّب بنفسك

fun main() {
    // كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
    val input = generateSequence(::readLine).toList()
    val words = ListSource(input[0].split(" "))
    val ints = ListSource(input[1].split(" ").map { it.toInt() })
    val counter = Counter()
    val allWords = Collector<String>()
    val numbers = Collector<Number>()
    println("words moved: " + pipe(words, allWords, counter))
    println("numbers moved: " + pipe(ints, numbers, counter))
    println(allWords.items)
    println(numbers.items)
    println("counted: ${counter.count}")
    println("after the end: ${words.next()}")
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: Playground لـ Kotlin