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

نمط المُزيّن

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

يضيف نمط المزيّن سلوكًا إلى كائن من خلال تغليفه بكائن آخر له الواجهة نفسها. يمرّر المزيّن الطلبات إلى الكائن الذي يغلّفه ويضيف الجزء الخاص به، لذا يمكن تكديس المزيّنات:

interface Coffee {
    fun cost(): Int
    fun description(): String
}
class Espresso : Coffee {
    override fun cost() = 200
    override fun description() = "espresso"
}
class Milk(private val inner: Coffee) : Coffee {
    override fun cost() = inner.cost() + 50
    override fun description() = inner.description() + ", milk"
}
class Syrup(private val inner: Coffee) : Coffee {
    override fun cost() = inner.cost() + 30
    override fun description() = inner.description() + ", syrup"
}

داخل main:

val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")

الناتج:

espresso, milk, milk, syrup: 330

عندما تغيّر المُزخرفات النتيجة، فإن ترتيبها مهم: يعمل كل واحد منها على ما أعاده الكائن الموجود بداخله:

interface Text {
    fun render(): String
}
class Word(private val w: String) : Text {
    override fun render() = w
}
class Bracket(private val inner: Text) : Text {
    override fun render() = "[" + inner.render() + "]"
}
class Twice(private val inner: Text) : Text {
    override fun render() = inner.render() + inner.render()
}

داخل main:

println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())

المخرجات:

[hi][hi]
[hihi]

باستخدام by، يكتب المفوِّض فقط الأعضاء التي يغيّرها ويمرّر بقية الأعضاء تلقائيًا:

interface Source {
    fun read(): String
    fun name(): String
}
class FileSource(private val content: String) : Source {
    override fun read() = content
    override fun name() = "notes.txt"
}
class Censored(private val inner: Source, private val word: String) : Source by inner {
    override fun read() = inner.read().replace(word, "***")
}

داخل main:

val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")

المخرَج:

notes.txt: the *** plan

يتم دمج الـ Decorators أثناء تشغيل البرنامج، على سبيل المثال استنادًا إلى اختيارات العميل، بينما يثبّت الصنف الفرعي تركيبة واحدة عند كتابة الكود. ستحتاج ثلاث إضافات كأصناف فرعية إلى صنف لكل تركيبة؛ أما كـ Decorators، فإن fold يكدّسها من قائمة:

interface Coffee {
    fun cost(): Int
}
class Espresso : Coffee {
    override fun cost() = 200
}
class Extra(private val inner: Coffee, private val price: Int) : Coffee {
    override fun cost() = inner.cost() + price
}

داخل main:

val prices = mapOf("milk" to 50, "syrup" to 30, "cream" to 70)
val choices = listOf("milk", "cream", "milk")
val coffee = choices.fold<String, Coffee>(Espresso()) { c, extra -> Extra(c, prices.getValue(extra)) }
println(coffee.cost())

الناتج:

370
challenge icon

التحدي

سهل

تُبنى البيتزا باستخدام مزينات للواجهة المتوفرة Pizza (مع price() وtoppings()). اكتب Base (بسعر 800 ومن دون إضافات) والمزينات Cheese (+150، وتضيف cheese) وOlives (+100، وتضيف olives) وDoubleUp، الذي يضاعف سعر كل ما بداخله ويكرر إضافاته مرتين. ثم اكتب build(names)، الذي يبدأ من Base ويغلّفه بمزين واحد لكل اسم، بالترتيب (cheese أو olives أو double).

يقرأ الكود المتوفر أسطرًا تحتوي على أسماء مفصولة بفواصل، أو plain، ثم يبني كل بيتزا ويطبع 1050: cheese, olives أو 800: plain.

ضع كودك في Base.kt وCheese.kt وOlives.kt وDoubleUp.kt وBuild.kt وPizza.kt. يحتوي Main.kt على كود الإدخال/الإخراج المتوفر، ولا يمكن تعديله.

جرّب بنفسك

fun main() {
    // كود الإدخال/الإخراج المُزوَّد: أبقِه كما هو
    val input = generateSequence(::readLine).toList()
    for (line in input) {
        val names = if (line == "plain") listOf() else line.split(",")
        val pizza = build(names)
        val toppings = pizza.toppings()
        println("${pizza.price()}: " + if (toppings.isEmpty()) "plain" else toppings.joinToString(", "))
    }
}
quiz iconاختبر نفسك

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

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

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