Kotlin: شرح تعبيرات الكائنات والكائنات المرافقة في كوتلن
آخر تحديث: 2026-08-26
كلمة object في كوتلن توحد ثلاثة أشكال: تصريحات الأحاديات، الكائنات المرافقة، والكائنات المجهولة. يستخدم تشارلي companion object كبديل لدوال static في جافا، و object كبديل لأصناف الأدوات الأحادية.
1. ما ستتعلمه
- تصريحات
object: نمط الأحادي في كوتلن companion object: دوال المصنع والبديل للأعضاء الثابتين- تعبيرات الكائنات: تطبيقات مجهولة
- امتدادات الكائن المرافق: دوال امتداد على مستوى الصنف
- تشارلي في العمل: مصنع
Order+ أحاديOrderValidator
2. قصة مهندس حقيقية
(1) نقطة الألم: تكاثر الأحاديات والدوال الثابتة في جافا
مشروع تشارلي في جافا كان يحتوي على أكثر من 10 أصناف أحادية (getInstance()) وسيل من دوال الأدوات الثابتة (static)، مما جعل الاختبار صعباً والامتداد مستحيلاً.
(2) حل object في كوتلن
KOTLIN
// جافا: أحادي مطوّل
public class OrderValidator {
private static final OrderValidator INSTANCE = new OrderValidator();
private OrderValidator() {}
public static OrderValidator getInstance() { return INSTANCE; }
}
// كوتلن: أحادي بسطر واحد
object OrderValidator {
fun validate(order: Order) = require(order.total >= 0)
}
سطر واحد من كوتلن يغني عن 5 أسطر من كود الأحادي في جافا، وهو آمن للخيوط بشكل طبيعي.
3. تصريح object (الأحادي)
(1) الصياغة الأساسية
KOTLIN
// أحادي: مثيل واحد فقط، تهيئة آمنة للخيوط
object OrderValidator {
fun validate(order: Order): Boolean {
return order.total >= 0 && order.id.startsWith("ORD-")
}
}
// الاستخدام: وصول مباشر باسم الكائن
OrderValidator.validate(order) // لا حاجة لـ getInstance()
(2) أحاديات بحالة
KOTLIN
object OrderCounter {
private var count = 0L
fun increment() { count++ }
fun getCount() = count
fun reset() { count = 0 }
}
OrderCounter.increment()
println(OrderCounter.getCount()) // 1
(3) object مقابل أحادي جافا
| البُعد | أحادي جافا | Kotlin object |
|---|---|---|
| حجم الكود | 5+ أسطر | سطر واحد |
| أمان الخيوط | يجب ضمانه يدوياً | آمن طبيعياً |
| التسلسل | يتطلب معالجة خاصة | صحيح طبيعياً |
| قابلية الاختبار | صعبة | صعبة كذلك (حالة عامة) |
| البديل | حقن التبعية | حقن التبعية (موصى به) |
4. companion object
(1) الصياغة الأساسية
KOTLIN
class Order(val id: String, val total: Double, val status: String) {
companion object {
// دوال المصنع
fun create(id: String, total: Double): Order =
Order(id, total, "PENDING")
fun fromCsv(line: String): Order {
val parts = line.split(",")
return Order(parts[0], parts[1].toDouble(), parts[2])
}
// ثوابت
const val DEFAULT_STATUS = "PENDING"
const val MAX_TOTAL = 1_000_000.0
}
}
// الاستخدام: مثل static في جافا
val order = Order.create("ORD-001", 299.99)
val csvOrder = Order.fromCsv("ORD-002,1500.00,CONFIRMED")
println(Order.DEFAULT_STATUS)
(2) كائنات مرافقة مسماة
KOTLIN
class Order(val id: String, val total: Double) {
companion object Factory {
fun create(id: String, total: Double) = Order(id, total)
}
}
// يمكن استخدام أي من الاسمين
Order.create("ORD-001", 299.99)
Order.Factory.create("ORD-001", 299.99)
(3) تطبيق companion object للواجهات
KOTLIN
interface JsonFactory<T> {
fun fromJson(json: String): T
}
class Order(val id: String, val total: Double) {
companion object : JsonFactory<Order> {
override fun fromJson(json: String): Order {
// تحليل json وإنشاء Order
return Order("ORD-from-json", 0.0)
}
}
}
// استخدام متعدد الأشكال
val factory: JsonFactory<Order> = Order
val order = factory.fromJson("{\"id\":\"ORD-001\"}")
(4) companion object مقابل static في جافا
| البُعد | Java static | Kotlin companion object |
|---|---|---|
| الطبيعة | عضو على مستوى الصنف | كائن (أحادي مرافق) |
| تطبيق الواجهات | لا يمكن | يمكن |
| دوال الامتداد | لا يمكن | يمكن |
| التسمية | بدون اسم | يمكن تسميته |
| الوجود وقت التشغيل | غير موجود | موجود (كائن أحادي) |
5. تعبيرات الكائنات (الكائنات المجهولة)
(1) الصياغة الأساسية
KOTLIN
// كائن مجهول: مثل الصنف الداخلي المجهول في جافا
val handler = object : OrderHandler {
override fun handle(order: Order) {
println("Handling ${order.id}")
}
}
// كائن مجهول يطبق واجهات متعددة
val multiHandler = object : OrderHandler, Loggable {
override fun handle(order: Order) { println("Handle: ${order.id}") }
override fun log(message: String) { println("[LOG] $message") }
}
(2) التقاط المتغيرات الخارجية بالكائنات المجهولة
KOTLIN
fun createHandler(prefix: String): OrderHandler {
var count = 0 // ملتقط بواسطة الكائن المجهول
return object : OrderHandler {
override fun handle(order: Order) {
count++
println("$prefix [#$count] ${order.id}")
}
}
}
(3) مقارنة أشكال object الثلاثة
| الشكل | الصياغة | دورة الحياة | حالة الاستخدام |
|---|---|---|---|
تصريح object |
object Name { } |
أحادي على مستوى العملية | أدوات عامة / إعدادات |
companion object |
class X { companion object { } } |
أحادي على مستوى الصنف | دوال المصنع / ثوابت |
| تعبير كائن | object : Interface { } |
مثيل مؤقت | تطبيقات لمرة واحدة |
6. مقارنة حالات الاستخدام العملية
| السيناريو | الشكل الموصى به | مثال |
|---|---|---|
| إعدادات عامة | تصريح object |
object AppConfig |
| دوال المصنع | companion object |
Order.create(...) |
| استدعاءات لمرة واحدة | تعبير كائن | object : OrderHandler { } |
| مجموعات ثابتة | companion object + const |
const val MAX = 1_000_000 |
| دوال الأدوات | تصريح object أو دالة مستوى أعلى |
object OrderUtils |
| تطبيق واجهات متعددة | تعبير كائن | object : A, B { } |
7. امتدادات الكائن المرافق
KOTLIN
class Order(val id: String, val total: Double) {
companion object // فارغ - موجود كهدف للامتداد
}
// امتداد على الكائن المرافق
fun Order.Companion.fromCsv(line: String): Order {
val parts = line.split(",")
return Order(parts[0], parts[1].toDouble())
}
// الاستخدام
val order = Order.fromCsv("ORD-001,299.99")
💡 تلميح: امتدادات الكائن المرافق تتيح للمكتبات الخارجية إضافة "دوال ثابتة" إلى صنف بدون تعديل كود مصدر الصنف.
7. مخطط اختيار شكل object
flowchart TD
A[تحتاج كائن؟] --> B{مرتبط strongly بصنف؟}
B -->|نعم| C{تحتاج مصنع/ثوابت؟}
B -->|لا| D{تحتاج مثيل لمرة واحدة؟}
C -->|نعم| E[companion object]
C -->|لا| F{تحتاج أحادي عام؟}
D -->|نعم| G[تعبير كائن<br/>object : Interface]
D -->|لا| H[مثيل صنف عادي]
F -->|نعم| I[تصريح object<br/>أحادي]
F -->|لا| H
8. مثال كامل
KOTLIN
// ============================================
// OrderProcessor - أنماط الكائنات
// الميزة: أحادي، مصنع مرافق، معالج مجهول
// ============================================
data class Order(val id: String, val total: Double, var status: String, val customer: String) {
companion object {
const val DEFAULT_STATUS = "PENDING"
const val MAX_TOTAL = 1_000_000.0
fun create(id: String, total: Double, customer: String): Order {
require(total >= 0) { "Total must be non-negative" }
require(total <= MAX_TOTAL) { "Total exceeds maximum" }
return Order(id, total, DEFAULT_STATUS, customer)
}
fun bulkCreate(vararg ids: String, total: Double, customer: String): List<Order> {
return ids.map { create(it, total, customer) }
}
}
}
// مدقق أحادي
object OrderValidator {
private val rules = mutableListOf<(Order) -> Boolean>()
fun addRule(rule: (Order) -> Boolean) { rules.add(rule) }
fun validate(order: Order): List<String> {
val errors = mutableListOf<String>()
if (!order.id.startsWith("ORD-")) errors.add("Invalid ID format")
if (order.total < 0) errors.add("Negative total")
rules.forEach { rule ->
if (!rule(order)) errors.add("Custom rule failed")
}
return errors
}
}
// عداد أحادي
object OrderMetrics {
private var totalProcessed = 0L
private var totalRevenue = 0.0
@Synchronized fun record(order: Order) {
totalProcessed++
totalRevenue += order.total
}
fun report() = "Processed: $totalProcessed orders, Revenue: \$$totalRevenue USD"
}
interface OrderHandler {
fun handle(order: Order): String
}
fun main() {
// مصنع مرافق
println("=== Factory Creation ===")
val order1 = Order.create("ORD-001", 299.99, "Alice")
val order2 = Order.create("ORD-002", 15_000.00, "Bob")
val bulkOrders = Order.bulkCreate("ORD-010", "ORD-011", "ORD-012", total = 500.0, customer = "Charlie")
println(order1)
println(order2)
bulkOrders.forEach { println(it) }
// مدقق أحادي
println("\n=== Validation ===")
OrderValidator.addRule { it.total > 0 }
val validOrder = Order("ORD-100", 299.99, "PENDING", "Alice")
val invalidOrder = Order("BAD-001", -50.0, "PENDING", "Bob")
println("Valid: ${OrderValidator.validate(validOrder)}")
println("Invalid: ${OrderValidator.validate(invalidOrder)}")
// مقاييس أحادية
println("\n=== Metrics ===")
listOf(order1, order2).forEach { OrderMetrics.record(it) }
println(OrderMetrics.report())
// معالج مجهول
println("\n=== Anonymous Handler ===")
val shippingHandler = object : OrderHandler {
private var shipped = 0
override fun handle(order: Order): String {
shipped++
order.status = "SHIPPED"
return "Shipped ${order.id} (Total shipped: $shipped)"
}
}
println(shippingHandler.handle(order1))
println(shippingHandler.handle(order2))
}
المخرجات:
TEXT
📖 للعرض فقط
=== Factory Creation ===
Order(id=ORD-001, total=299.99, status=PENDING, customer=Alice)
Order(id=ORD-002, total=15000.0, status=PENDING, customer=Bob)
Order(id=ORD-010, total=500.0, status=PENDING, customer=Charlie)
Order(id=ORD-011, total=500.0, status=PENDING, customer=Charlie)
Order(id=ORD-012, total=500.0, status=PENDING, customer=Charlie)
=== Validation ===
Valid: []
Invalid: [Invalid ID format, Negative total, Custom rule failed]
=== Metrics ===
Processed: 2 orders, Revenue: $15299.99 USD
=== Anonymous Handler ===
Shipped ORD-001 (Total shipped: 1)
Shipped ORD-002 (Total shipped: 2)
9. أمثلة عملية سريعة
▶ مثال: تعبير object (مجهول)
KOTLIN
// واجهة بسيطة
interface EventHandler {
fun handle(event: String)
}
// استخدام تعبير object لإنشاء implementation فوري
val handler = object : EventHandler {
override fun handle(event: String) {
println("Handling: $event")
}
}
handler.handle("user-login")
handler.handle("order-placed")
// تعبير object مع خصائص
val counter = object {
var count = 0
fun increment() { count++ }
}
counter.increment()
counter.increment()
println("Counter: ${counter.count}")
// تعبير object كقيمة مرجعة
fun createLogger(prefix: String): Any {
return object {
fun log(message: String) {
println("[$prefix] $message")
}
}
}
val logger = createLogger("APP")
// logger.log("Hello") // لا يعمل - النوع Any لا يعرض log()
**المخرجات:
TEXT
📖 للعرض فقط
Handling: user-login
Handling: order-placed
Counter: 2
▶ مثال: companion object
KOTLIN
class Database private constructor(val url: String) {
companion object {
private val instances = mutableMapOf<String, Database>()
const val MAX_CONNECTIONS = 100
fun connect(url: String): Database {
return instances.getOrPut(url) { Database(url) }
}
fun getActiveConnections(): Int = instances.size
// @JvmStatic لتوليد static method في JVM
@JvmStatic
fun isValidUrl(url: String): Boolean = url.startsWith("jdbc:")
}
fun query(sql: String) = "[$url] $sql"
}
// الوصول بدون instance
val db1 = Database.connect("jdbc:mysql://localhost/test")
val db2 = Database.connect("jdbc:mysql://localhost/test")
println("Same: ${db1 === db2}") // true - singleton
println("Max: ${Database.MAX_CONNECTIONS}")
println("Active: ${Database.getActiveConnections()}")
println("Valid: ${Database.isValidUrl("jdbc:test")}")
**المخرجات:
TEXT
📖 للعرض فقط
Same: true
Max: 100
Active: 1
Valid: true
▶ مثال: object declaration (singleton)
KOTLIN
// object = singleton مع lazy initialization
object AppConfig {
val appName: String = "OrderProcessor"
val version: String = "1.0.0"
val environment: String = "production"
private val settings = mutableMapOf<String, String>()
fun set(key: String, value: String) {
settings[key] = value
}
fun get(key: String): String? = settings[key]
}
println("App: ${AppConfig.appName} v${AppConfig.version}")
println("Env: ${AppConfig.environment}")
AppConfig.set("api_key", "secret")
AppConfig.set("max_connections", "100")
println("API key: ${AppConfig.get("api_key")}")
// object مع واجهة
interface Logger {
fun log(level: String, message: String)
}
object ConsoleLogger : Logger {
override fun log(level: String, message: String) {
println("[$level] $message")
}
}
val logger: Logger = ConsoleLogger
logger.log("INFO", "Application started")
**المخرجات:
TEXT
📖 للعرض فقط
App: OrderProcessor v1.0.0
Env: production
API key: secret
[INFO] Application started
▶ مثال: companion object مع @JvmStatic
KOTLIN
class MathUtils private constructor() {
companion object {
@JvmStatic
val PI: Double = 3.14159265358979
@JvmStatic
fun square(n: Int): Int = n * n
@JvmStatic
fun cube(n: Int): Int = n * n * n
// بدون @JvmStatic - تابع مثيل
fun power(base: Int, exponent: Int): Int {
var result = 1
repeat(exponent) { result *= base }
return result
}
}
}
// الاستخدام من Kotlin
println("PI: ${MathUtils.PI}")
println("Square 5: ${MathUtils.square(5)}")
println("Cube 3: ${MathUtils.cube(3)}")
println("Power 2^10: ${MathUtils.power(2, 10)}")
// من Java:
// MathUtils.square(5); // static (مع @JvmStatic)
// MathUtils.Companion.square(5); // بدون @JvmStatic
**المخرجات:
TEXT
📖 للعرض فقط
PI: 3.14159265358979
Square 5: 25
Cube 3: 27
Power 2^10: 1024
▶ مثال: nested object
KOTLIN
class Outer {
private val outerValue = "outer"
// object عادي nested
object Nested {
fun greet(): String = "Hello from Nested"
// لا يمكن الوصول لـ outerValue مباشرة - object ثابت
}
// companion object - له صلاحية الوصول لـ outer
companion object {
private val companionValue = "companion"
fun accessFromCompanion(): String {
// خطأ - لا يمكن الوصول لـ outerValue مباشرة
// return outerValue // ❌
return companionValue
}
}
// object داخل دالة - له closure
fun createListener(): Any {
var count = 0
return object {
fun increment(): Int {
count++
println("Count: $count")
return count
}
fun getCount() = count
}
}
}
println(Outer.Nested.greet())
println(Outer.accessFromCompanion())
val listener = Outer().createListener()
listener.increment()
listener.increment()
**المخرجات:
TEXT
📖 للعرض فقط
Hello from Nested
companion
Count: 1
Count: 2
▶ مثال: enum class مع companion object
KOTLIN
enum class Status(val code: Int, val message: String) {
PENDING(0, "Waiting"),
PROCESSING(1, "Working"),
COMPLETED(2, "Done"),
FAILED(3, "Error");
companion object {
fun fromCode(code: Int): Status? = values().firstOrNull { it.code == code }
fun activeStatuses(): List<Status> = values().filter { it != COMPLETED && it != FAILED }
}
fun isTerminal(): Boolean = this == COMPLETED || this == FAILED
}
// الاستخدام
println("From code 1: ${Status.fromCode(1)}")
println("Active: ${Status.activeStatuses()}")
println("COMPLETED is terminal: ${Status.COMPLETED.isTerminal()}")
println("PENDING is terminal: ${Status.PENDING.isTerminal()}")
for (status in Status.values()) {
println("${status.name} (${status.code}): ${status.message}")
}
**المخرجات:
TEXT
📖 للعرض فقط
From code 1: PROCESSING
Active: [PENDING, PROCESSING]
COMPLETED is terminal: true
PENDING is terminal: false
PENDING (0): Waiting
PROCESSING (1): Working
COMPLETED (2): Done
FAILED (3): Error
▶ مثال: data object (Kotlin 1.9+)
KOTLIN
// object عادي
object RegularObject {
val name = "regular"
override fun toString() = "Regular(name=$name)"
}
// data object - equals/hashCode/toString تلقائي
data object DataObject {
val name = "data"
}
println(RegularObject)
println(DataObject)
val obj1 = RegularObject
val obj2 = RegularObject
println("Regular equal: ${obj1 == obj2}") // true (singleton)
val data1 = DataObject
val data2 = DataObject
println("Data equal: ${data1 == data2}") // true (لكن equals مُولّد)
// في قائمة
val objects = listOf(RegularObject, RegularObject)
val dataObjects = listOf(DataObject, DataObject)
println("Regular unique: ${objects.distinct().size}") // 1
println("Data unique: ${dataObjects.distinct().size}") // 1 (مُحسّن)
// النسخ
// val copy = DataObject.copy() // لا يعمل لـ data object (لا خصائص قابلة للنسخ)
**المخرجات:
TEXT
📖 للعرض فقط
Regular(name=regular)
DataObject(name=data)
Regular equal: true
Data equal: true
Regular unique: 1
Data unique: 1
❓ أسئلة شائعة
س هل يمكن لأحاديات object قبول حقن التبعية؟
ج تقنياً نعم (عبر خصائص
var)، لكنه غير موصى به. الحالة العامة للأحادي تجعل الاختبار صعباً. لكود الإنتاج، فضّل أطر حقن التبعية (Koin/Dagger) على أحاديات object.س هل أعضاء الكائن المرافق ثابتون فعلاً؟
ج على JVM، التوابع المزينة بـ
@JvmStatic داخل كائن مرافق تُترجم كتوابع ثابتة حقيقية. بدون التعليق، تبقى توابع مثيل على الكائن المرافق.س هل يمكن للكائنات المجهولة الوصول للمتغيرات الخارجية؟
ج نعم. على عكس الأصناف الداخلية المجهولة في جافا، يمكن للكائنات المجهولة في كوتلن الوصول وتعديل متغيرات
var الخارجية (مثل الإغلاقات).س كيف أختار بين companion object وتصريح object؟
ج استخدم
companion object للأشياء المقترنة strongly بصنف (دوال مصنع، ثوابت). استخدم تصريحات object للأشياء المستقلة عن أي صنف (أدوات عامة، إعدادات).س ما الفرق بين
const val و val العادي في كائن مرافق؟ج
const val ثابت وقت الترجمة (مُضمّن في مواقع الاستدعاء)، قابل للتطبيق فقط على الأنواع الأولية و String. val العادي يُهيأ وقت التشغيل.س هل يمكن للكائن المرافق أن يكون له نوع أعلى؟
ج نعم. يمكن للكائنات المرافقة تطبيق واجهات، مما يجعل نمط المصنع أكثر أناقة:
companion object : JsonFactory<Order>.📖 ملخص
- تصريحات
objectتنشئ أحاديات آمنة للخيوط، بديل لأكثر من 5 أسطر من كود الأحادي في جافا companion objectبديلstaticفي جافا، بقدرات أكبر (واجهات، امتدادات، تسمية)- تعبيرات الكائنات
object : Interface { }بديل الأصناف الداخلية المجهولة في جافا - امتدادات الكائن المرافق تتيح إضافة "دوال ثابتة" بدون تعديل كود المصدر
- الأحاديات صعبة الاختبار؛ فضّل حقن التبعية على أحاديات object في الإنتاج
const valللثوابت وقت الترجمة مقابلvalللتهيئة وقت التشغيل
📝 تمارين
- مبتدئ (⭐): أنشئ أحادي
OrderCounterبتوابعincrement()وgetCount(). تلميح:object OrderCounter { ... } - متوسط (⭐⭐): أضف
companion objectإلى صنفOrderبدالة مصنعfromCsvتحلل سطر CSV لإنشاء طلب. تلميح:companion object { fun fromCsv(line: String): Order { ... } } - متقدم (⭐⭐⭐): نفّذ واجهة
JsonFactory<Order>للكائن المرافق + امتداد كائن مرافقOrder.Companion.fromXml(). تلميح:companion object : JsonFactory<Order> { ... }+ دالة امتداد على مستوى أعلى