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

التوابع الساكنة والخاصة في الواجهات

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

التوابع الساكنة والخاصة في الواجهات

أضاف Java 8 التوابع الافتراضية (default) إلى الواجهات. وأكمل إضافتان لاحقتان هذه الصورة: التوابع الساكنة (Java 8) والتوابع الخاصة (Java 9). تتيح هذه الأنواع مجتمعةً للواجهات حمل منطق مساعد حقيقي دون تلويث الفئات المنفِّذة لها.

التوابع الساكنة في الواجهات

التابع الساكن في الواجهة ينتمي إلى الواجهة ذاتها لا إلى أي فئة منفِّذة. تستدعيه باسم الواجهة تمامًا كالتابع الساكن في الفئات — InterfaceName.method().

public interface Validator<T> { boolean isValid(T value); // تابع مساعد ساكن — يُستدعى بالشكل Validator.requireNonNull(...) static <T> void requireNonNull(T value, String fieldName) { if (value == null) { throw new IllegalArgumentException(fieldName + " must not be null"); } } }

يمكن لأي فئة استدعاء Validator.requireNonNull(email, "email") دون الحاجة إلى تنفيذ Validator. لاحظ أن التابع مساعد تحمله الواجهة بذاتها؛ ولا يمكن للفئات المنفِّذة تجاوزه (override).

التوابع الساكنة لا تُوَرَّث. إذا كانت EmailValidator implements Validator<String>، فلا يمكنك كتابة EmailValidator.requireNonNull(...). يجب أن يمر الاستدعاء عبر Validator.requireNonNull(...). هذا مقصود — يمنع الغموض حين تنفّذ فئة واجهات متعددة تحمل كل منها تابعًا ساكنًا بالاسم ذاته.

لماذا نضع التوابع المساعدة الساكنة في الواجهة؟

قبل Java 8، كان النمط المعياري هو فئة مساعدة مرافقة — مثل Collections جنبًا إلى جنب مع Collection، أو Objects مع Object. هذا يعمل، لكنه يفصل الكود ذا الصلة إلى ملفين. تتيح التوابع الساكنة في الواجهات إبقاء منطق الأداة المساعدة بجوار العقد الذي تدعمه مباشرةً:

public interface Discount { double apply(double price); // توابع ساكنة لإنشاء نسخ — تبقى منطق الإنشاء مرتبطًا بالعقد static Discount percentage(double pct) { return price -> price * (1 - pct / 100); } static Discount fixed(double amount) { return price -> Math.max(0, price - amount); } static Discount none() { return price -> price; } } // الاستخدام Discount tenPercent = Discount.percentage(10); System.out.println(tenPercent.apply(200.0)); // 180.0
استخدم التوابع الساكنة في الواجهات كتوابع مصنع (factory methods). هي بديل أنيق لفئة منفصلة مثل DiscountUtils أو Discounts. والنتيجة قابلة للاكتشاف — أي بيئة تطوير تكتب فيها Discount. تعرض المصانع المتاحة فورًا.

التوابع الخاصة في الواجهات (Java 9+)

حين وصلت التوابع الافتراضية (default)، ظهرت مشكلة سريعًا: تابعان افتراضيان أحيانًا يشتركان في منطق مكرر، لكن لم يكن ثمة مكان لوضع مساعد مشترك داخل الواجهة دون الكشف عنه كجزء من العقد العام. حلّ Java 9 هذا بـالتوابع الخاصة.

public interface Logger { void log(String message); default void info(String message) { log(format("INFO", message)); } default void warn(String message) { log(format("WARN", message)); } default void error(String message) { log(format("ERROR", message)); } // مساعد خاص — تفصيل تنفيذي، ليس جزءًا من الواجهة العامة private String format(String level, String message) { return "[" + level + "] " + message; } }

تابع format تفصيل تنفيذي. الفئات المنفِّذة لن تراه أبدًا — لا يظهر في الواجهة العامة. إن أردت لاحقًا تغيير صيغة النص، تغيّر تابعًا واحدًا بدلًا من ثلاثة.

التوابع الخاصة الساكنة في الواجهات

يمكنك أيضًا جعل التابع الخاص ساكنًا. القاعدة تشبه ما تعرفه من الفئات: التابع الخاص غير الساكن لا يُستدعى إلا من التوابع الأخرى للنسخة (أي توابع default هنا)، بينما يمكن استدعاء التابع الخاص الساكن من كلٍّ من توابع default والتوابع الساكنة.

public interface Codec { String encode(String input); String decode(String input); static Codec base64() { return new Base64Codec(); } static boolean isSupportedCharset(String charset) { return isAsciiSubset(charset); // استدعاء مساعد خاص ساكن } // خاص ساكن — قابل للاستخدام من التوابع الساكنة لهذه الواجهة private static boolean isAsciiSubset(String name) { return name != null && name.matches("[A-Za-z0-9\\-]+"); } }
التوابع الخاصة لا يمكن استدعاؤها من الفئات المنفِّذة. توجد فقط لتقليل التكرار داخل توابع default والساكنة الخاصة بالواجهة. محاولة استدعاء تابع خاص في الواجهة من فئة منفِّذة هي خطأ وقت الترجمة.

تجميع الصورة كاملة

إليك مثال مكتفٍ بذاته يستخدم الأنواع الأربعة من التوابع — abstract وdefault وstatic وprivate — في واجهة واحدة:

public interface PriceCalculator { // مجرد (abstract) — يجب تنفيذه double basePrice(); // افتراضي — سلوك مشترك، قابل للتجاوز default double withTax(double taxRate) { return round(basePrice() * (1 + taxRate / 100)); } default double discounted(double pct) { return round(basePrice() * (1 - pct / 100)); } // ساكن كمصنع static PriceCalculator of(double price) { return () -> price; } // خاص — يُزيل التكرار بين التوابع الافتراضية private double round(double value) { return Math.round(value * 100.0) / 100.0; } } // الاستخدام PriceCalculator laptop = PriceCalculator.of(999.99); System.out.println(laptop.withTax(15)); // 1149.99 System.out.println(laptop.discounted(10)); // 899.99

الخلاصة

  • التوابع الساكنة (Java 8) — تنتمي للواجهة لا للنسخ؛ تُستدعى عبر Interface.method()؛ ممتازة لتوابع المصنع والمساعدات النقية.
  • التوابع الخاصة (Java 9) — مرئية فقط داخل الواجهة؛ تُزيل التكرار عبر توابع default.
  • التوابع الخاصة الساكنة (Java 9) — كالتوابع الخاصة لكن قابلة للاستدعاء من التوابع الساكنة أيضًا.
  • لا التوابع الساكنة ولا الخاصة جزء من العقد — الفئات المنفِّذة لا ترثها ولا تتجاوزها.