الواجهات والأصناف المجرّدة

الثوابت والأنواع المتداخلة في الواجهات

15 دقيقة الدرس 8 من 14

الثوابت والأنواع المتداخلة في الواجهات

تعرف بالفعل أن الواجهات تُعرّف عقود التوابع. ما يغفله كثير من المطورين هو أن الواجهة يمكنها أيضًا أن تحمل ثوابت بل وتصريحات أنواع متداخلة — واجهات داخلية وتعدادات وفئات. فهم هذه الميزات يتيح لك استخدام الواجهات كفضاءات أسماء مركّزة للقيم المرتبطة وأنواع المساعدة، مما يُبقي تصميمك متماسكًا.

حقول الواجهة دائمًا public static final

كل حقل تصرّح به داخل واجهة يكون ضمنيًا public static final، سواء كتبت هذه المعدِّلات أم لا. هذا يجعل كل حقل في الواجهة ثابتًا في وقت التصريف.

public interface HttpStatus { // المعدِّلات الثلاثة ضمنية — كتابتها مسموحة لكنها زائدة. int OK = 200; int CREATED = 201; int BAD_REQUEST = 400; int UNAUTHORIZED = 401; int NOT_FOUND = 404; }

بما أن الحقول static فإنك تصل إليها عبر اسم الواجهة: HttpStatus.OK. وبما أنها final فلا يستطيع أي صنف تنفيذي تغييرها — فهي مُدمجة في وقت التصريف.

المصرّف يُطبّق العقد. إذا حاولت الإسناد إلى حقل واجهة — HttpStatus.OK = 500; — يرفض المصرّف بالخطأ "cannot assign a value to final variable". لا توجد طريقة لتجاوز ثابت الواجهة في النوع الفرعي.

لماذا نستخدم ثوابت الواجهة؟

حالة الاستخدام الأساسية هي تجميع الثوابت المرتبطة دلاليًا مع الواجهة التي تحكم السلوك الذي تصفه. مثلًا، واجهة Validator قد تجمع رموز أخطائها:

public interface Validator<T> { String validate(T value); String ERR_BLANK = "Value must not be blank"; String ERR_TOO_LONG = "Value exceeds maximum length"; String ERR_INVALID = "Value contains invalid characters"; }

أي صنف ينفّذ Validator يستطيع الإشارة إلى ERR_BLANK مباشرة (الثوابت موروثة في النطاق)، وأي صنف آخر يستطيع الإشارة إليها كـ Validator.ERR_BLANK.

فضّل التعدادات للمجموعات الثابتة المُسمّاة. إذا كانت ثوابتك تمثّل مجموعة مغلقة من الاختيارات (مثل أساليب HTTP، أيام الأسبوع)، فإن enum — ربما متداخل داخل الواجهة — أأمن وأكثر تعبيرًا من ثوابت int أو String الخام، لأن المصرّف يستطيع فحص فروع switch بشكل شامل ومنع تمرير قيم غير صالحة.

نمط الواجهة الثابتة المضاد

بما أن الفئات التي تنفّذ واجهة ترث ثوابتها، تعرّف بعض قواعد الكود القديمة واجهات ثوابت خالصة — واجهات بلا توابع، فقط حقول — كاختصار لتجنب كتابة اسم الواجهة كبادئة:

// نمط مضاد: لا تفعل هذا public interface MagicNumbers { int MAX_RETRIES = 3; int TIMEOUT_MS = 5000; } public class RetryService implements MagicNumbers { // يمكن كتابة MAX_RETRIES مباشرة بدلاً من MagicNumbers.MAX_RETRIES }
نمط الواجهة الثابتة يُسرّب تفاصيل التنفيذ. تنفيذ واجهة هو التزام عام يصبح جزءًا من API الصنف. إذا أزلت الواجهة لاحقًا، تنكسر التوافقية الثنائية. استخدم بدلًا من ذلك فئة مساعدة final بمنشئ خاص، أو enum مناسب. يتناول جوشوا بلوك هذا الأمر في Effective Java البند 22.

الأنواع المتداخلة داخل الواجهة

تسمح Java بتداخل فئة أو واجهة أو تعداد داخل واجهة. الأنواع المتداخلة تكون ضمنيًا public static. تخدم كمساعدين مرتبطين أو عقود فرعية تنتمي منطقيًا إلى الواجهة الخارجية.

التعداد المتداخل — الحالة الأكثر شيوعًا

يوفر التعداد المتداخل مجموعة آمنة للنوع من القيم التي تعمل عليها الواجهة:

public interface Shape { enum Kind { CIRCLE, RECTANGLE, TRIANGLE } Kind kind(); double area(); double perimeter(); } public class Circle implements Shape { private final double radius; public Circle(double radius) { this.radius = radius; } @Override public Kind kind() { return Kind.CIRCLE; } @Override public double area() { return Math.PI * radius * radius; } @Override public double perimeter() { return 2 * Math.PI * radius; } }

يشير المستدعون إلى التعداد كـ Shape.Kind.CIRCLE. التعداد مرتبط بشكل دائم بالواجهة — إشارة تصميمية واضحة بأن Kind لا معنى له خارج Shape.

الواجهة المتداخلة — عقد فرعي

تداخل واجهة داخل أخرى يُنمذج قدرة اختيارية أو مكمّلة:

public interface Repository<T, ID> { T findById(ID id); void save(T entity); // بعض المستودعات فقط تدعم ترقيم الصفحات interface Pageable<T> { java.util.List<T> findPage(int page, int size); long count(); } } // مستودع يدعم أيضًا ترقيم الصفحات public class UserRepository implements Repository<User, Long>, Repository.Pageable<User> { @Override public User findById(Long id) { /* ... */ return null; } @Override public void save(User user) { /* ... */ } @Override public java.util.List<User> findPage(int page, int size) { /* ... */ return null; } @Override public long count() { /* ... */ return 0; } }

الفئة المتداخلة — إرفاق منشئ بالواجهة

الفئة المتداخلة داخل واجهة نادرة لكنها عملية عندما تريد إرفاق منشئ مرافق أو مصنع مباشرة بجانب النوع الذي ينشئه:

public interface Notification { String getMessage(); String getRecipient(); class Builder { private String message; private String recipient; public Builder message(String message) { this.message = message; return this; } public Builder recipient(String recipient) { this.recipient = recipient; return this; } public Notification build() { String m = message; String r = recipient; return new Notification() { @Override public String getMessage() { return m; } @Override public String getRecipient() { return r; } }; } } } // الاستخدام — المنشئ موجود تمامًا حيث ستبحث عنه Notification n = new Notification.Builder() .recipient("alice@example.com") .message("Your order shipped!") .build();

مثال واقعي شامل

تجمع واجهة PaymentGateway التالية ثوابت وتعدادًا متداخلًا وفئة نتيجة متداخلة في مكان واحد:

public interface PaymentGateway { // ثوابت وقت التصريف التي تصف سياسة إعادة المحاولة int MAX_RETRIES = 3; long TIMEOUT_MS = 5_000L; // رموز نتائج آمنة للنوع enum Status { SUCCESS, DECLINED, ERROR, TIMEOUT } // كائن قيمة يُرسل مع الحالة class ChargeResult { public final Status status; public final String transactionId; public final String message; public ChargeResult(Status status, String transactionId, String message) { this.status = status; this.transactionId = transactionId; this.message = message; } } ChargeResult charge(String customerId, long amountCents, String currency); }

كل قطعة معرفة تتعلق بالشحن — ثابت المهلة الزمنية، ومفردات النتيجة، وبنية النتيجة — تعيش داخل العقد الذي يُعرّف الشحن. تستورد التنفيذات فقط ما تحتاجه؛ ويقرأ المستدعون كل شيء من مكان واحد.

الخلاصة

  • حقول الواجهة دائمًا public static final؛ وهي ثوابت وقت التصريف.
  • احتفظ بالثوابت في الواجهة فقط عندما تكون جزءًا حقيقيًا من عقدها. تجنّب واجهات الثوابت الخالصة.
  • التعدادات المتداخلة هي النوع المتداخل الأكثر شيوعًا — فهي تعبّر عن مجموعة مغلقة من القيم المرتبطة بالواجهة.
  • الواجهات المتداخلة تُنمذج عقودًا فرعية اختيارية أو مكمّلة.
  • الفئات المتداخلة (المنشئات، المصانع، كائنات القيمة) ترسل أدوات مرافقة مباشرة بجانب العقد الذي تخدمه.
  • جميع الأنواع المتداخلة داخل واجهة هي ضمنيًا public static.