Interface: الجوهر، العقود في Java و Kotlin لنظام Android

المؤلف: IT Sectr نُشر: 2026-02-18 وقت القراءة: 9 دق

Interface هو عقد يحدد مجموعة من الطرق المجردة التي يجب على الفصل تنفيذها. في Java و Kotlin، الواجهات هي الآلية الرئيسية للتجريد وتعدد الأشكال. في Java 8+، يمكن للواجهات احتواء طرق default و static، في Kotlin — تطبيقات افتراضية. وفقًا لـ Google Android Developers (2025)، تُستخدم الواجهات في 90% من مشاريع Android لتحديد طبقات الهندسة المعمارية — المستودعات و UseCases والخدمات.

النقاط الرئيسية

  • Interface هو نوع مجرد يحدد تواقيع الطرق دون تطبيق (قبل Java 8)
  • Implements هي الكلمة المفتاحية التي تربط الفصل بالواجهة؛ يمكن للفصل تنفيذ واجهات متعددة
  • Default method هو طريقة مع تطبيق في واجهة Java، أُضيف في Java 8 للتوافق مع الإصدارات السابقة
  • Kotlin interface تدعم الخصائص مع getters وتطبيقات الطرق، لتحل محل abstract class في العديد من السيناريوهات
  • Markup interface هي واجهة فارغة تُستخدم كعلامة (Serializable, Cloneable, RandomAccess)

ما هو Interface في Java و Kotlin؟

Interface هو نوع مرجعي يحتوي على طرق مجردة وثوابت وطرق افتراضية. يجب على الفصل الذي ينفذ واجهة أن يوفر تطبيقًا لجميع طرقها المجردة. في Java، لا يمكن للواجهة أن تحتوي على حالة (حقول مثيل)؛ Kotlin تلتزم أيضًا بهذا القيد ولكنها تدعم الخصائص مع المُوصِلات.

Java
public interface Repository {
    T findById(Long id);
    List findAll();
    T save(T entity);
    void deleteById(Long id);
    
    default long count() {
        return findAll().size();
    }
}

@Entity
public class UserEntity {
    private Long id;
    private String email;
    // getters و setters
}

public class UserRepositoryImpl implements Repository {
    private final EntityManager em;
    
    public UserEntity findById(Long id) {
        return em.find(UserEntity.class, id);
    }
    
    public List findAll() {
        return em.createQuery("FROM UserEntity", UserEntity.class)
                .getResultList();
    }
    // الطرق الأخرى
}

Repository<T> هي واجهة عامة مع طرق CRUD. الطريقة الافتراضية count() توفر تطبيقًا افتراضيًا يمكن تجاوزه. UserRepositoryImpl ينفذ الواجهة باستخدام EntityManager للوصول إلى البيانات. هذا النهج يسمح باختبار طبقة البيانات من خلال mock للواجهة بدون قاعدة بيانات حقيقية.

Interface vs abstract class: متى تختار ماذا

الاختيار بين الواجهة والفصل المجرد يعتمد على وجود حالة مشتركة والعلاقات بين الأنواع. الواجهة تحدد عقدًا (ما يمكن للفصل فعله)، الفصل المجرد يحدد تطبيقًا مشتركًا (ما هو الفصل).

المعيارInterfaceAbstract Class
الحالة (الحقول)فقط ثوابت static finalنعم، أي حقول
المنشئاتلانعم
الوراثة المتعددةنعم (implements)لا (extends واحد)
معدّلات الوصولpublic (Java 8-)، طرق defaultالكل (private, protected, public)
متى تستخدمعقد للفصول غير المرتبطةقاعدة مشتركة للفصول ذات الصلة

في Clean Architecture، تُوضع الواجهات في الطبقة الداخلية (domain)، والتطبيقات في الطبقة الخارجية (data). هذا يسمح بالحفاظ على قاعدة التبعية: الطبقة الخارجية تعتمد على الداخلية، وليس العكس. الفصول المجردة تُستخدم غالبًا للطرق النموذجية (Template Method pattern).

Interface في Kotlin: الخصائص والمفوضون

واجهات Kotlin أكثر مرونة من Java: يمكنها تعريف خصائص مجردة وتوفير تطبيقات للطرق. على عكس Java، تدعم Kotlin التفويض عبر الكلمة المفتاحية by، مما يقلل بشكل كبير من الكود التكراري عند تنفيذ نمط Delegate.

Kotlin
interface ApiService {
    val baseUrl: String  // خاصية مجردة
    
    suspend fun fetchData(): Result>
    
    fun getEndpoint(path: String): String {
        return "$baseUrl/$path"  // تطبيق افتراضي
    }
}

class RetrofitApiService(
    override val baseUrl: String
) : ApiService {
    private val client = Retrofit.Builder()
        .baseUrl(baseUrl)
        .build()
    
    override suspend fun fetchData(): Result> {
        // تنفيذ الطلب
    }
}

// التفويض عبر by
interface Logger {
    fun log(message: String)
}

class ConsoleLogger : Logger {
    override fun log(message: String) = println(message)
}

class UserService(logger: Logger) : Logger by logger

UserService ينفذ واجهة Logger من خلال التفويض (by). جميع استدعاءات log() تُوجّه إلى كائن logger دون كتابة طرق غلاف. هذا مثال على التركيب الذي سيتطلب في Java 5-10 أسطر من الكود التكراري.

Interface في Clean Architecture Android

Clean Architecture لنظام Android تفصل التطبيق بوضوح إلى طبقات. تعمل الواجهات كحدود بين الطبقات: Domain يحدد واجهات المستودعات و UseCases، Data يوفر التطبيقات. هذا يسمح باستبدال التطبيقات دون تغيير منطق الأعمال — ميزة رئيسية عند الانتقال من Room إلى Firebase أو من REST إلى GraphQL.

Kotlin
// Domain layer — واجهة (عقد)
interface UserRepository {
    suspend fun getUser(id: String): User
    suspend fun updateUser(user: User)
}

// Domain layer — use case (يعتمد على الواجهة)
class GetUserUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(id: String): Result {
        return runCatching { repository.getUser(id) }
    }
}

// Data layer — تطبيق
class UserRepositoryImpl(
    private val api: UserApi,
    private val dao: UserDao
) : UserRepository {
    override suspend fun getUser(id: String): User {
        val cached = dao.getUser(id)
        if (cached != null) return cached
        val remote = api.fetchUser(id)
        dao.insertUser(remote)
        return remote
    }
    // updateUser...
}

UserRepository هي واجهة في طبقة domain. GetUserUseCase يعتمد على الواجهة، وليس على التطبيق. UserRepositoryImpl في طبقة data ينفذ الواجهة، ليجمع بين API وقاعدة البيانات المحلية. هذه الهندسة تسمح باختبار UseCases مع مستودع mock دون إعداد قاعدة بيانات أو شبكة.

الطرق default و static في Java 8+

الطرق default أُضيفت في Java 8 للتوسع التدريجي للواجهات دون كسر التوافق مع الإصدارات السابقة. إذا لم ينفذ ArrayList الطريقة الجديدة stream() المضافة إلى Collection، لكان الكود القديم استمر في العمل. الطرق static في الواجهات تخدم كأدوات مساعدة متعلقة بالواجهة — بديل لفئات الأدوات المساعدة.

Java
public interface Vehicle {
    void start();
    void stop();
    
    default void honk() {
        System.out.println("Beep!");
    }
    
    static Vehicle of(String type) {
        if ("car".equals(type)) return new Car();
        return new Bicycle();
    }
    
    // ثابت
    String CATEGORY = "transport";
}

الطرق default تحل diamond problem: إذا نفذ فصل واجهتين بنفس الطريقة default، يتطلب المترجم تجاوزًا صريحًا. الطرق static تُستدعى عبر اسم الواجهة — Vehicle.of("car")، بدون مثيل.

الأخطاء الشائعة في تصميم الواجهات

الأخطاء في تصميم الواجهات تؤدي إلى كود هش، وتعقيد في الاختبار، وانتهاكات SOLID. دعنا نستعرض ثلاث مشكلات شائعة.

تلوث الواجهة — عدد كبير جدًا من الطرق في واجهة واحدة

الواجهة التي تحتوي على 15+ طريقة تنتهك Interface Segregation Principle (ISP). مثال على ذلك java.util.Dictionary القديم بـ 10+ طرق. الحل: التقسيم إلى عدة واجهات صغيرة — ReadableRepository, WritableRepository, SearchableRepository. العميل (الخدمة) يعتمد فقط على الطرق التي يحتاجها.

التجريد المفرط — واجهة لكل فصل

إنشاء واجهة لكل فصل دون حاجة حقيقية لتعدد الأشكال هو النمط المضاد Interface overkill. المؤشر: الواجهة لها تطبيق واحد بالضبط، وليس للمشروع خطط لإضافة بدائل. الحل: إضافة واجهة فقط عندما يظهر خيار تطبيق ثانٍ أو عند الحاجة إلى mock للاختبار.

الأسئلة الشائعة

كيف يختلف interface عن abstract class في Java؟

Interface يحدد فقط عقدًا (تواقيع طرق)، ولا يمكنه احتواء حالة، ويدعم الوراثة المتعددة. Abstract class يمكن أن يحتوي على حقول ومنشئات وطرق مطبقة، لكن يمكن للفصل أن يرث فصلًا مجردًا واحدًا فقط. منذ Java 8، حصلت الواجهات على طرق default و static، مما قلص الفجوة.

هل يمكن للواجهة أن ترث واجهة أخرى؟

نعم، في Java و Kotlin تدعم الواجهات الوراثة. public interface AdvancedRepository<T> extends Repository<T>, Pageable هي واجهة تجمع بين اثنتين أخريين. الفصل الذي ينفذ AdvancedRepository يجب أن ينفذ جميع طرق كلا الواجهتين الأم. الوراثة المتعددة مسموح بها فقط للواجهات.

ما هي الواجهة الوظيفية في Java؟

الواجهة الوظيفية هي واجهة بطريقة مجردة واحدة (SAM — Single Abstract Method). التعليق التوضيحي @FunctionalInterface يضمن هذا القيد. أمثلة: Runnable, Callable, Comparator, Consumer. الواجهات الوظيفية هي أساس تعبيرات lambda في Java 8: () -> System.out.println() ينفذ Runnable.

لماذا نحتاج إلى طرق default في واجهات Java؟

الطرق default تسمح بإضافة طرق جديدة إلى واجهة دون تعديل جميع الفصول المنفذة. على سبيل المثال، Java 8 أضافت stream() إلى Collection كطريقة default. بدون هذه الآلية، foreach() و stream() وطرق أخرى كانت ستتطلب تغييرات في آلاف الفصول في JDK. Default هي طريقة توسع متوافقة مع الإصدارات السابقة.

كيف تحل Kotlin مشكلة الوراثة المتعددة؟

Kotlin تمنع الوراثة المتعددة للفصول ولكنها تسمح بالتنفيذ المتعدد للواجهات. إذا كانت لواجهتين طريقة بنفس التوقيع والتطبيق الافتراضي، يتطلب المترجم تجاوزًا صريحًا باستدعاء super<InterfaceName>.method(). هذا يحل diamond problem على مستوى التجميع.

الملخص

  • Interface هو عقد يحدد الطرق التي يجب على الفصل تنفيذها؛ الآلية الرئيسية لتعدد الأشكال في Java و Kotlin
  • Java 8+ أضافت طرق default و static إلى الواجهات، مما قلص الفجوة مع الفصول المجردة
  • Kotlin interfaces تدعم الخصائص المجردة والتطبيقات الافتراضية والتفويض عبر by
  • Clean Architecture تستخدم الواجهات كحدود بين طبقات Domain و Data
  • Interface Segregation Principle يتطلب تقسيم الواجهات الكبيرة إلى متخصصة
  • الواجهات الوظيفية (Single Abstract Method) هي أساس تعبيرات lambda و Stream API
  • توصية: أضف واجهة عندما يظهر تطبيق ثانٍ أو عند الحاجة إلى mock للاختبار

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا