نمط المُزيّن
جزء من قسم البرمجة كائنية التوجه في رحلة 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التحدي
سهلتُبنى البيتزا باستخدام مزينات للواجهة المتوفرة 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(", "))
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1الفئات والكائنات
العمل مع الملفاتتعريف الفئاتالمنشئات الأساسيةكتل init والتحقق من الصحةالمنشئات الثانويةمراجعة - بطاقة المكتبة4الوراثة
الأصناف المفتوحة والأصناف الفرعيةتجاوز الأعضاءsuper والمنشئاتالأصناف المجرّدةمراجعة - دليل الموظفينتدرّب بنفسك: Playground لـ Kotlin