Interface هو عقد يحدد مجموعة من الطرق المجردة التي يجب على الفصل تنفيذها. في Java و Kotlin، الواجهات هي الآلية الرئيسية للتجريد وتعدد الأشكال. في Java 8+، يمكن للواجهات احتواء طرق default و static، في Kotlin — تطبيقات افتراضية. وفقًا لـ Google Android Developers (2025)، تُستخدم الواجهات في 90% من مشاريع Android لتحديد طبقات الهندسة المعمارية — المستودعات و UseCases والخدمات.
النقاط الرئيسية
Interface هو نوع مرجعي يحتوي على طرق مجردة وثوابت وطرق افتراضية. يجب على الفصل الذي ينفذ واجهة أن يوفر تطبيقًا لجميع طرقها المجردة. في Java، لا يمكن للواجهة أن تحتوي على حالة (حقول مثيل)؛ Kotlin تلتزم أيضًا بهذا القيد ولكنها تدعم الخصائص مع المُوصِلات.
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 | Abstract Class |
|---|---|---|
| الحالة (الحقول) | فقط ثوابت static final | نعم، أي حقول |
| المنشئات | لا | نعم |
| الوراثة المتعددة | نعم (implements) | لا (extends واحد) |
| معدّلات الوصول | public (Java 8-)، طرق default | الكل (private, protected, public) |
| متى تستخدم | عقد للفصول غير المرتبطة | قاعدة مشتركة للفصول ذات الصلة |
في Clean Architecture، تُوضع الواجهات في الطبقة الداخلية (domain)، والتطبيقات في الطبقة الخارجية (data). هذا يسمح بالحفاظ على قاعدة التبعية: الطبقة الخارجية تعتمد على الداخلية، وليس العكس. الفصول المجردة تُستخدم غالبًا للطرق النموذجية (Template Method pattern).
واجهات Kotlin أكثر مرونة من Java: يمكنها تعريف خصائص مجردة وتوفير تطبيقات للطرق. على عكس Java، تدعم Kotlin التفويض عبر الكلمة المفتاحية by، مما يقلل بشكل كبير من الكود التكراري عند تنفيذ نمط Delegate.
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 loggerUserService ينفذ واجهة Logger من خلال التفويض (by). جميع استدعاءات log() تُوجّه إلى كائن logger دون كتابة طرق غلاف. هذا مثال على التركيب الذي سيتطلب في Java 5-10 أسطر من الكود التكراري.
Clean Architecture لنظام Android تفصل التطبيق بوضوح إلى طبقات. تعمل الواجهات كحدود بين الطبقات: Domain يحدد واجهات المستودعات و UseCases، Data يوفر التطبيقات. هذا يسمح باستبدال التطبيقات دون تغيير منطق الأعمال — ميزة رئيسية عند الانتقال من Room إلى Firebase أو من REST إلى GraphQL.
// 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 أُضيفت في Java 8 للتوسع التدريجي للواجهات دون كسر التوافق مع الإصدارات السابقة. إذا لم ينفذ ArrayList الطريقة الجديدة stream() المضافة إلى Collection، لكان الكود القديم استمر في العمل. الطرق static في الواجهات تخدم كأدوات مساعدة متعلقة بالواجهة — بديل لفئات الأدوات المساعدة.
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 8، حصلت الواجهات على طرق default و static، مما قلص الفجوة.
نعم، في Java و Kotlin تدعم الواجهات الوراثة. public interface AdvancedRepository<T> extends Repository<T>, Pageable هي واجهة تجمع بين اثنتين أخريين. الفصل الذي ينفذ AdvancedRepository يجب أن ينفذ جميع طرق كلا الواجهتين الأم. الوراثة المتعددة مسموح بها فقط للواجهات.
الواجهة الوظيفية هي واجهة بطريقة مجردة واحدة (SAM — Single Abstract Method). التعليق التوضيحي @FunctionalInterface يضمن هذا القيد. أمثلة: Runnable, Callable, Comparator, Consumer. الواجهات الوظيفية هي أساس تعبيرات lambda في Java 8: () -> System.out.println() ينفذ Runnable.
الطرق default تسمح بإضافة طرق جديدة إلى واجهة دون تعديل جميع الفصول المنفذة. على سبيل المثال، Java 8 أضافت stream() إلى Collection كطريقة default. بدون هذه الآلية، foreach() و stream() وطرق أخرى كانت ستتطلب تغييرات في آلاف الفصول في JDK. Default هي طريقة توسع متوافقة مع الإصدارات السابقة.
Kotlin تمنع الوراثة المتعددة للفصول ولكنها تسمح بالتنفيذ المتعدد للواجهات. إذا كانت لواجهتين طريقة بنفس التوقيع والتطبيق الافتراضي، يتطلب المترجم تجاوزًا صريحًا باستدعاء super<InterfaceName>.method(). هذا يحل diamond problem على مستوى التجميع.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.