تجاوز المحتوى

شرح قاعدة Open/Closed Principle

اسم القاعدة: Open/Closed Principle وتختصر OCP

تنص هذه القاعدة على أن الكلاس أو الميثود يجب أن تكون قابلة للإضافات لكن غير قابلة للتعديل. والمقصود هنا أنه إذا احتجنا لميزة جديدة فنكتفي بالوراثة وإضافة الميزة الجديدة بدون تعديل على الكلاس أو الميثودز الأساسية. وباستخدام الوراثة يمكن إضافة ميثودز جديدة أو عمل override لميثودز في الكلاس الأساسية.

ومن أسباب تطبيق هذه القاعدة ما يلي‫:‬

  • الكلاس الأساسية تم اختبارها مسبقا وتم التأكد من سلامتها، فأي تعديل عليها يوجب علينا إعادة اختبار الكلاس والتأكد من سلامتها من الأخطاء والمشاكل.
  • الكلاس الأساسية تم استخدامها في عدة أجزاء من البرنامج، و أي تعديل عليها قد يسبب خلل في تلك الأجزاء مما يوجب علينا تعديلات على تلك الأجزاء ثم إعادة اختبار تلك الاجزاء والتأكد من سلامتها من الأخطاء والمشاكل.

وهذا مثال كلاسيكي على برنامج يخالف القاعدة بحيث سنضطر لتعديل البرنامج في كل مرة نحتاج لميزة جديدة:

public class Shape {

}
public class Rectangle extends Shape {
    private double width;
    private double height;

    public double getWidth();
    public double getHeight();
}
public class Circle extends Shape {
    private double radius;

    public double getRadius();
}
public class AreaManager {
    public double area(Shape[] shapes) {
        double area = 0;

        for (int i = 0; i < shapes.length; i++) {
            Shape shape = shapes[i];

            if (shape instanceof Rectangle) {
                double height = ((Rectangle) shape).getHeight();
                double length = ((Rectangle) shape).getWidth();

                area += (height * length);
            } else if (shape instanceof Circle) {
                double radius = ((Circle) shape).getRadius();

                area += (radius * radius * Math.PI);
            }
        }

        return area;
    }
}

الآن لو احتجنا إضافة ميزة جديدة للبرنامج وهي إضافة شكل جديد، مثلاً المثلث وحساب مساحته فسنضطر لإجراء تعديل على كلاس AreaManager في ميثود area كما يلي:

public class Triangle extends Shape {
    private double base;
    private double height;

    public double getBase();
    public double getHeight();
}
public class AreaManager {
    public double area(Shape[] shapes) {
        double area = 0;

        for (int i = 0; i < shapes.length; i++) {
            Shape shape = shapes[i];

            if (shape instanceof Rectangle) {
                double height = ((Rectangle) shape).getHeight();
                double length = ((Rectangle) shape).getWidth();

                area += (height * length);
            } else if (shape instanceof Circle) {
                double radius = ((Circle) shape).getRadius();

                area += (radius * radius * Math.PI);
            } else if (shape instanceof Triangle) { // <--- Added to support Triangle shapes
                double base = ((Triangle) shape).getBase();
                double height = ((Triangle) shape).getHeight();
                
                area += (base * height / 2);
            }
        }

        return area;
    }
}

وهكذا سيكون الحال مع كل إضافة لشكل جديد. ولتفادي التعديلات يمكن إعادة تصميم البرنامج ليتوافق مع القاعدة ولا نحتاج لتعديلات مستقبلية مهما أضفنا من الأشكال الجديدة.

المثال بعد إعادة تصميمه، حيث جعلنا كل كلاس تقوم بحساب مساحتها بنفسها. وكلاس AreaManager لن تحتاج لإي تعديل في المستقبل.

التصحيح الأول وهو تعديل كلاسات الأشكال وإضافة ميثود لحساب المساحة لكل كلاس يتم وراثتها من الكلاس الأساسي:

abstract class Shape {
    abstract public double area(); // <-- Added to avoid future changes in AreaManager
}
class Rectangle extends Shape {
    private double width;
    private double height;

    public double getWidth();
    public double getHeight();

    @Override
    public double area() { // <-- Overrides the abstract method
        return height * width;
    }
}
class Circle extends Shape {
    private double radius;

    public double getRadius();

    @Override
    public double area() { // <-- Overrides the abstract method
        return radius * radius * Math.PI;
    }
}
class Triangle extends Shape {
    private double base;
    private double height;

    public double getBase();

    public double getHeight();

    @Override
    public double area() { // <-- Overrides the abstract method
        return base * height / 2;
    }
}

التصحيح الثاني وهو تعديل كلاس AreaManager لتستفيد من ميثود حساب المساحة التي أضفناها في كلاسات الأشكال:

public class AreaManager {
    public double area(Shape[] shapes) {
        double area = 0;
        // We deal with all shape instances equally, by using Shape::area() method
        for (int i = 0; i < shapes.length; i++) {
            Shape shape = shapes[i];

            area += shape.area();
        }

        return area;
    }
}

أصبح برنامجنا الآن يقبل إضافة ميزات جديدة بدون الحاجة لتعديل في الميزات الحالية. فيمكن إضافة ألف شكل جديد وحساب مساحتها بدون تعديل على كلاس AreaManager.

ولتطبيق هذا القاعدة يمكنك أن تسأل نفسك السؤالين التاليين:

  1. هل يمكن إضافة ميزة جديدة للبرنامج أو للكلاس؟
  2. هل سأحتاج تعديل في الكود عند إضافة هذه الميزة الجديدة؟

هذه الأسئلة هي الأساسية في تحديد هل تم تطبيق هذه القاعدة أم لا.

فإذا كان جواب الأول بـ "نعم"، فلابد أن يكون جواب الثاني بـ "لا" حتى تكون طبقت هذه القاعدة وإلا فإنك ستحتاج إجراء تعديل على برنامجك.

نصيحة: دائماً قم بإجراء التعديلات في البداية، لأن أي تأخير قد يسبب في زيادة كمية التعديلات التي ستجريها مستقبلاً. وضع الحكمة التالية في بالك دائماً:

Later means never

Published inبرمجة

كن أول من ‫يعلق على المقالة

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *