CodeGym /مدونة جافا /Random-AR /الصلبة: خمسة مبادئ أساسية لتصميم الفصل في جافا
John Squirrels
مستوى
San Francisco

الصلبة: خمسة مبادئ أساسية لتصميم الفصل في جافا

نشرت في المجموعة
الطبقات هي اللبنات الأساسية للتطبيقات. تماما مثل الطوب في المبنى. يمكن للفصول المكتوبة بشكل سيء أن تسبب مشاكل في النهاية. الصلبة: خمسة مبادئ أساسية لتصميم الفصل في جافا - 1لفهم ما إذا كان الفصل مكتوبًا بشكل صحيح، يمكنك التحقق من مدى مطابقته لـ "معايير الجودة". في Java، هذه هي ما يسمى بالمبادئ الصلبة، وسوف نتحدث عنها.

المبادئ الصلبة في جافا

SOLID هو اختصار مكون من الحروف الكبيرة للمبادئ الخمسة الأولى لـ OOP وتصميم الفصل. تم التعبير عن المبادئ بواسطة روبرت مارتن في أوائل العقد الأول من القرن الحادي والعشرين، ثم تم تقديم الاختصار لاحقًا بواسطة مايكل فيذرز. فيما يلي المبادئ الصلبة:
  1. مبدأ المسؤولية الفردية
  2. فتح المبدأ المغلق
  3. مبدأ استبدال ليسكوف
  4. مبدأ فصل الواجهة
  5. مبدأ انعكاس التبعية

مبدأ المسؤولية الفردية (SRP)

ينص هذا المبدأ على أنه لا ينبغي أبدًا أن يكون هناك أكثر من سبب واحد لتغيير الفصل. يتحمل كل كائن مسؤولية واحدة، وهي مغلفة بالكامل في الفصل. تهدف جميع خدمات الفصل إلى دعم هذه المسؤولية. سيكون من السهل دائمًا تعديل مثل هذه الفئات إذا لزم الأمر، لأنه من الواضح ما هو الفصل وما هو غير المسؤول عنه. بمعنى آخر، سنكون قادرين على إجراء تغييرات وعدم الخوف من العواقب، أي التأثير على الأشياء الأخرى. بالإضافة إلى ذلك، يعد اختبار هذه التعليمات البرمجية أسهل بكثير، لأن اختباراتك تغطي جزءًا واحدًا من الوظائف بمعزل عن جميع الوظائف الأخرى. تخيل وحدة تقوم بمعالجة الطلبات. إذا تم تشكيل الطلب بشكل صحيح، فإن هذه الوحدة تحفظه في قاعدة بيانات وترسل بريدًا إلكترونيًا لتأكيد الطلب:
public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
يمكن أن تتغير هذه الوحدة لثلاثة أسباب. أولاً، قد يتغير منطق معالجة الطلبات. ثانيًا، قد تتغير طريقة حفظ الأوامر (نوع قاعدة البيانات). ثالثًا، قد تتغير طريقة إرسال التأكيد (على سبيل المثال، لنفترض أننا بحاجة إلى إرسال رسالة نصية بدلاً من البريد الإلكتروني). ويعني مبدأ المسؤولية الواحدة أن الجوانب الثلاثة لهذه المشكلة هي في الواقع ثلاث مسؤوليات مختلفة. وهذا يعني أنهم يجب أن يكونوا في فئات أو وحدات مختلفة. يعتبر الجمع بين العديد من الكيانات التي يمكن أن تتغير في أوقات مختلفة ولأسباب مختلفة قرارًا سيئًا للتصميم. من الأفضل تقسيم الوحدة إلى ثلاث وحدات منفصلة، ​​تؤدي كل منها وظيفة واحدة:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

المبدأ المفتوح المغلق (OCP)

يوصف هذا المبدأ على النحو التالي: يجب أن تكون الكيانات البرمجية (الفئات، الوحدات، الوظائف، وما إلى ذلك) مفتوحة للتوسيع، ولكنها مغلقة للتعديل . هذا يعني أنه من الممكن تغيير السلوك الخارجي للفصل دون إجراء تغييرات على التعليمات البرمجية الموجودة للفصل. وفقًا لهذا المبدأ، تم تصميم الفئات بحيث يتطلب تعديل الفئة لتناسب ظروف معينة توسيعها وتجاوز بعض الوظائف. وهذا يعني أن النظام يجب أن يكون مرنًا وقادرًا على العمل في الظروف المتغيرة دون تغيير كود المصدر. استمرارًا لمثالنا الذي يتضمن معالجة الطلب، لنفترض أننا بحاجة إلى تنفيذ بعض الإجراءات قبل معالجة الطلب وكذلك بعد إرسال رسالة التأكيد عبر البريد الإلكتروني. بدلًا من تغيير OrderProcessorالفئة نفسها، سنقوم بتمديدها لتحقيق هدفنا دون انتهاك المبدأ المفتوح المغلق:
public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }

    private void beforeProcessing() {
        // Take some action before processing the order
    }

    private void afterProcessing() {
        // Take some action after processing the order
    }
}

مبدأ استبدال ليسكوف (LSP)

هذا هو الاختلاف في المبدأ المفتوح المغلق الذي ذكرناه سابقًا. ويمكن تعريفه على النحو التالي: يمكن استبدال الكائنات بكائنات من الفئات الفرعية دون تغيير خصائص البرنامج. وهذا يعني أن الفئة التي تم إنشاؤها عن طريق توسيع فئة أساسية يجب أن تتجاوز أساليبها بحيث لا يتم المساس بالوظيفة من وجهة نظر العميل. أي أنه إذا قام أحد المطورين بتوسيع فصلك واستخدامه في أحد التطبيقات، فلا ينبغي عليه تغيير السلوك المتوقع لأي من الأساليب التي تم تجاوزها. يجب أن تتجاوز الفئات الفرعية أساليب الفئة الأساسية حتى لا يتم كسر الوظيفة من وجهة نظر العميل. يمكننا استكشاف هذا بالتفصيل في المثال التالي. لنفترض أن لدينا فئة مسؤولة عن التحقق من صحة الطلب والتحقق مما إذا كانت جميع البضائع الموجودة في الطلب متوفرة في المخزون. تحتوي هذه الفئة على isValid()طريقة تُرجع صوابًا أو خطأً :
public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (!item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
لنفترض أيضًا أن بعض الطلبات تحتاج إلى التحقق من صحتها بشكل مختلف عن الطلبات الأخرى، على سبيل المثال بالنسبة لبعض الطلبات نحتاج إلى التحقق مما إذا كانت جميع البضائع الموجودة في الطلب موجودة في المخزون وما إذا كانت جميع البضائع معبأة. للقيام بذلك، نقوم بتوسيع OrderStockValidatorالفصل عن طريق إنشاء OrderStockAndPackValidatorالفصل:
public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
لكننا هنا انتهكنا مبدأ استبدال ليسكوف، لأنه بدلاً من إرجاع خطأ إذا فشل التحقق من صحة الأمر، فإن طريقتنا تلقي خطأ IllegalStateException. لا يتوقع العملاء الذين يستخدمون هذا الرمز هذا: فهم يتوقعون قيمة إرجاع إما true أو false . هذا يمكن أن يؤدي إلى أخطاء وقت التشغيل.

مبدأ فصل الواجهة (ISP)

يتميز هذا المبدأ بالبيان التالي: لا ينبغي إجبار العميل على تنفيذ أساليب لن يستخدمها . ويعني مبدأ فصل الواجهة أن الواجهات "السميكة" للغاية يجب تقسيمها إلى واجهات أصغر وأكثر تحديدًا، بحيث لا يعرف العملاء الذين يستخدمون واجهات صغيرة سوى الأساليب التي يحتاجونها لعملهم. ونتيجة لذلك، عندما يتغير أسلوب الواجهة، يجب ألا يتغير أي عملاء لا يستخدمون هذا الأسلوب. خذ بعين الاعتبار هذا المثال: قام Alex، أحد المطورين، بإنشاء واجهة "تقرير" وأضاف طريقتين: generateExcel()و generatedPdf(). الآن يريد العميل استخدام هذه الواجهة، ولكنه ينوي فقط استخدام التقارير بتنسيق PDF، وليس في Excel. هل هذه الوظيفة ترضي هذا العميل؟ لا، سيتعين على العميل تنفيذ طريقتين، إحداهما غير مطلوبة إلى حد كبير وهي موجودة فقط بفضل Alex، الشخص الذي صمم البرنامج. سيستخدم العميل إما واجهة مختلفة أو لن يفعل شيئًا باستخدام طريقة تقارير Excel. إذن ما هو الحل؟ إنه تقسيم الواجهة الحالية إلى واجهتين أصغر. واحد لتقارير PDF، والآخر لتقارير Excel. يتيح ذلك للعملاء استخدام الوظائف التي يحتاجونها فقط.

مبدأ انعكاس التبعية (DIP)

في Java، يتم وصف مبدأ SOLID على النحو التالي: يتم بناء التبعيات داخل النظام بناءً على التجريدات . لا تعتمد الوحدات ذات المستوى الأعلى على الوحدات ذات المستوى الأدنى. لا ينبغي أن تعتمد التجريدات على التفاصيل. التفاصيل يجب أن تعتمد على التجريدات. يجب تصميم البرنامج بحيث تكون الوحدات المختلفة مستقلة بذاتها ومتصلة ببعضها البعض من خلال التجريد. التطبيق الكلاسيكي لهذا المبدأ هو Spring Framework. في Spring Framework، يتم تنفيذ جميع الوحدات كمكونات منفصلة يمكن أن تعمل معًا. إنها مستقلة جدًا بحيث يمكن استخدامها بنفس السهولة في وحدات البرنامج بخلاف Spring Framework. يتم تحقيق ذلك بفضل الاعتماد على المبادئ المغلقة والمفتوحة. توفر جميع الوحدات الوصول فقط إلى التجريد، والذي يمكن استخدامه في وحدة أخرى. دعونا نحاول توضيح ذلك باستخدام مثال. عند الحديث عن مبدأ المسؤولية الفردية، أخذنا في الاعتبار الفصل OrderProcessor. دعونا نلقي نظرة أخرى على رمز هذه الفئة:
public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
في هذا المثال، OrderProcessorيعتمد فصلنا على فئتين محددتين: MySQLOrderRepositoryو ConfirmationEmailSender. سنقدم أيضًا رمز هذه الفئات:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
هذه الفئات بعيدة كل البعد عن ما نسميه التجريدات. ومن وجهة نظر مبدأ انعكاس التبعية، سيكون من الأفضل البدء بإنشاء بعض التجريدات التي يمكننا العمل بها في المستقبل، بدلاً من تنفيذ تطبيقات محددة. لنقم بإنشاء واجهتين: MailSenderو OrderRepository). ستكون هذه تجريداتنا:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
نقوم الآن بتنفيذ هذه الواجهات في الفئات التي تم إعدادها بالفعل لهذا الغرض:
public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}
لقد قمنا بالأعمال التحضيرية بحيث OrderProcessorلا يعتمد صفنا على تفاصيل ملموسة، بل على التجريدات. سنقوم بتغييره عن طريق إضافة تبعياتنا إلى مُنشئ الفصل:
public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
الآن يعتمد فصلنا على التجريدات، وليس على تطبيقات محددة. يمكننا بسهولة تغيير سلوكه عن طريق إضافة التبعية المطلوبة في وقت OrderProcessorإنشاء الكائن. لقد قمنا بفحص مبادئ التصميم SOLID في Java. ستتعلم المزيد عن OOP بشكل عام وأساسيات برمجة Java - لا شيء ممل ومئات الساعات من التدريب - في دورة CodeGym. حان الوقت لحل بعض المهام :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION