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

شرح قاعدة Single Responsibility Principle

اسم القاعدة: Single Responsibility Principle وتختصر SRP

يقصد بهذه القاعدة أن كل كلاس وكل ميثود يجب أن يكون لها مسؤولية واحدة فقط.

سنكتب مثال كلاس Employee يخالف هذه القاعدة، ثم سنطبق القاعدة عليها ونستخرج المُخالفات، ثم سنقوم بإجراء عدة تعديلات لنحصل على مثال متوافق مع القاعدة.

class Employee {
    private int id;
    private String name;
    private double salary;

    public int getId();
    public void setId(int id);

    public String getName();
    public void setName(String name);

    public double getSalary();
    public void setSalary(double salary);

    // save this employee object into database (add new or update current)
    public boolean saveEmployee();

    // delete this employee object from the database
    public boolean deleteEmployee();

    // search the database for an employee whose id matches the given id
    public static Employee getEmployee(int id);

    // sort the given list of employees
    public static List sortEmployees(List employeeList);

    // search the given in-memory employeeList for an employee whose name matches the given employeeName
    public static Employee findEmployee(List employeeList, String employeeName);
}
class Employee {
    private var id: Int
    private var name: Int
    private var salary: Double
    
    func getId() -> Int
    func setId(id: Int)
    
    func getName() -> String
    func setName(name: String)
    
    func getSalary() -> Double
    func setSalary(salary: Double)
    
    // save this employee object into database (add new or update current)
    func saveEmployee() -> Bool
    
    // delete this employee object from the database
    func deleteEmployee() -> Bool
    
    // search the database for an employee whose id matches the given id
    func getEmployee(id: Int) -> Employee
    
    // sort the given list of employees
    func sortEmployees(employees: [Employee]) -> [Employee]
    
    // search the given in-memory employeeList for an employee whose name matches the given employeeName
    func findEmployee(employees: [Employee], employeeName: String) -> Employee
}

من أول نظرة لهذه الكلاس نجد أنها كلاس خاصة فقط بالموظف والعمليات القائمة على الموظف والموظفين. وقد يظهر لنا أنها تتوافق مع القاعدة. لكن في الحقيقة هذه الكلاس تخالف القاعدة بسبب تعدد مسؤولياتها:

  • مسؤولة عن الموظف كأوبجكت.
  • مسؤولة عن عمليات قاعدة البيانات على الموظف.
  • مسؤولة عن عمليات أخرى تجرى على قائمة (مصفوفة) الموظفين.

وبناءً على هذه المسؤوليات المتعددة سنقوم بتقسيم هذه الكلاس إلى عدة كلاسات كما يلي:

  1. الكلاس الأول يكون تحت اسم Employee وهو عبارة عن كلاس POJO عادي للموظف بدون أي عمليات.
class Employee {
    private int id;
    private String name;
    private double salary;

    public int getId();
    public int setId(int id);

    public String getName();
    public void setName(String name);

    public double getSalary();
    public void setSalary(double salary);
}
class Employee {
    private var id: Int
    private var name: Int
    private var salary: Double
    
    func getId() -> Int
    func setId(id: Int)
    
    func getName() -> String
    func setName(name: String)
    
    func getSalary() -> Double
    func setSalary(salary: Double)
}
  1. الكلاس الثاني يكون تحت اسم EmployeeModel ويمثل كافة العمليات الخاصة بالموظف في قاعدة البيانات. الإضافة والقراءة والحذف والتعديل والبحث… إلخ
class EmployeeModel {
    // save given employee object into database (add new or update current)
    public boolean saveEmployee(Employee employee);

    // delete given employee object from the database
    public boolean deleteEmployee(Employee employee);

    // search the database for an employee whose id matches the given id
    public static Employee getEmployee(int id);
}
class EmployeeModel {
    // save given employee object into database (add new or update current)
    func saveEmployee(employee: Employee) -> Bool
    
    // delete given employee object from the database
    func deleteEmployee(employee: Employee) -> Bool
    
    // search the database for an employee whose id matches the given id
    func getEmployee(id: Int) -> Employee
}
  1. الكلاس الثالث يكون تحت مسمى EmployeeUtils ويحتوي على العمليات التي نجريها على الموظف خارج قاعدة البيانات، مثلاً البحث في قائمة من الموظفين أو ترتيبها.
class EmployeeUtils {
    // sort the given list of employees
    public static List sortEmployees(List employeeList);

    // search the given in-memory employeeList for an employee whose name matches the given employeeName
    public static Employee findEmployee(List employeeList, String employeeName);
}
class EmployeeUtils {
    // sort the given list of employees
    func sortEmployees(employees: [Employee]) -> [Employee]
    
    // search the given in-memory employeeList for an employee whose name matches the given employeeName
    func findEmployee(employees: [Employee], employeeName: String) -> Employee
}

لهذا التقسيم العديد من الفوائد التي تكتشفها عند العمل على مشاريع حقيقية. ومنها:

  • سهولة إجراء التعديلات المستقبلية.
  • سهولة حل المشكلات.
  • سهولة قراءة البرنامج ومعرفة عمل كل جزء بدون تعقيد وخلط بين الوظائف.
  • عزل كلاس Employee عن العمليات الأخرى يحميك من أي تعديلات غير مقصودة عند إرسال الاوبجكت لكلاس آخر في نفس التطبيق.

مثال على مشكلة في النسخة الأولى من كلاس Employee:
أولاً نعلم أن النسخة الأولى من الكلاس تضم كافة المسؤوليات في كلاس واحد.
ثانياً استخدمنا هذه الكلاس في أجزاء مختلفة من برنامجنا، عمليات قاعدة البيانات، عمليات البحث و الترتيب، واستخدمناها كناقل لأوبجكت الموظفين في عدد من الجزئيات.

الآن عندما نجري تغيير على أي جزء في الكلاس فإننا سنضطر لاختبار كافة الجزئيات التي تستخدم كلاسنا هذه وقد تظهر عدة مشاكل هنا وهناك.

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

الآن عندما نجري تغيير على كلاس عمليات قاعدة البيانات فسنختبر فقط جزئية قاعدة البيانات في البرنامج دون الحاجة لاختبار الجزئيات الأخرى لأنها لن تتأثر من هذا التغيير.

ملاحظة: التسمية التي استخدمناها في الكلاسات لا يجب أن تكون دائماً هكذا. هي مجرد تفضيل شخصي. مثلاً يمكن تغيير EmployeeModel إلى EmployeeTable وتغيير EmployeeUtils إلى EmployeeOperations

يمكن ترجمة هذه القاعدة إلى العربية كالتالي:
قاعدة المسؤولية الواحدة

Published inبرمجة

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

اترك تعليقاً

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