البرمجة الوظيفية مقابل البرمجة كائنية التوجه في Java: ماذا يختار المطور الحديث؟
\n\nعالم Java هو ساحة معركة بين عملاقين. من ناحية، لدينا البرمجة كائنية التوجه (OOP) الكلاسيكية التي نشأ عليها جيل كامل من المطورين. ومن ناحية أخرى، المنهج الوظيفي الذي اقتحم النظام البيئي مع إصدار Java 8، وما زالت النقاشات محتدمة: "هل نحتاج إليه حقًا؟".
\n\nإذا كنت تكتب بلغة Java، فأنت تواجه هذا الاختيار يوميًا. هل يجب إنشاء كلاس كامل مع دوال لفلترة بسيطة؟ أم الأفضل استخدام سلسلة من لامبدا ونسيان الأمر؟ في هذا المقال، لن ننحاز إلى أي جانب. سنحلل كلا المنهجين بأمثلة واقعية، ونقارنهما وفق معايير رئيسية، ونساعدك على فهم متى تستخدم كل أداة من أدواتك.
\n\nنبذة تاريخية: كيف وصلنا إلى هنا؟
\n\nصُممت Java في الأصل كلغة برمجة كائنية التوجه بحتة. كل شيء هو كائن. حتى النص هو كائن من الكلاس String. الكلاسات، الوراثة، تعدد الأشكال — كانت هذه هي العقيدة. لكن مع مرور الوقت، أدرك المطورون أن OOP ممتازة في نمذجة العالم الحقيقي (سيارات، موظفون، طلبات)، لكنها سيئة في معالجة تدفقات البيانات.
هنا ظهرت تعبيرات لامبدا وStream API. لم تصبح Java لغة وظيفية (مثل Haskell أو Scala)، لكنها أخذت أفضل ما في هذه البرمجة. الآن لدينا خيار: كتابة حلقة for داخل دالة كلاس، أو استخدام filter().map().collect().
المنهج كائني التوجه: الكلاسيكي الأصيل
\n\nتقوم OOP في Java على أربع ركائز: التغليف، الوراثة، تعدد الأشكال، والتجريد. عندما تعمل مع منطق الأعمال الذي يحتوي على كيانات (مستخدم، طلب، منتج)، فإن OOP هي الخيار الطبيعي.
\n\nإليك مثال لمهمة كلاسيكية: إيجاد مجموع جميع طلبات العميل التي تمت في عام 2024.
\n\nالحل كائني التوجه:
\n\nimport java.util.List;\n\npublic class OrderService {\n\n public double calculateTotal2024(List<Order> orders) {\n double total = 0.0;\n for (Order order : orders) {\n if (order.getYear() == 2024) {\n total += order.getAmount();\n }\n }\n return total;\n }\n}\n\nclass Order {\n private int year;\n private double amount;\n\n public Order(int year, double amount) {\n this.year = year;\n this.amount = amount;\n }\n\n public int getYear() { return year; }\n public double getAmount() { return amount; }\n}\n\nكل شيء واضح، قابل للقراءة، وسهل التصحيح. أي مطور مبتدئ سيفهم ما يحدث هنا. لكن هناك نقطة: الكود أمرّي (Imperative). نحن نقول كيف نفعل (أنشئ متغيرًا، تجول في القائمة، تحقق من الشرط، اجمع).
\n\nالمنهج الوظيفي: نكتب بشكل تصريحي
\n\nالبرمجة الوظيفية في Java تدور حول ماذا نريد الحصول عليه، وليس كيف. نستخدم دوال عالية المستوى، والثبات (Immutability)، وغياب الآثار الجانبية.
\n\nنفس المهمة، ولكن بأسلوب وظيفي:
\n\nالحل الوظيفي:
\n\nimport java.util.List;\n\npublic class OrderServiceFunctional {\n\n public double calculateTotal2024(List<Order> orders) {\n return orders.stream()\n .filter(order -> order.getYear() == 2024)\n .mapToDouble(Order::getAmount)\n .sum();\n }\n}\n\nثلاثة أسطر فقط. لا متغيرات قابلة للتغيير، ولا حلقات. نحن فقط نقول: "خذ التدفق، صفّي حسب السنة، خذ المبالغ، اجمعها". هذا هو الأسلوب التصريحي (Declarative).
\n\nالمقارنة: أين يتفوق كل منهج؟
\n\nدعنا نفصل السيناريوهات التي يظهر فيها كل منهج بشكل أفضل.
\n\n1. قابلية القراءة والإيجاز
\nالفائز: المنهج الوظيفي