انتقال للمقال
وقت القراءة: ≈ 5 دقائق

مبدأ الـ Interface Segregation

السلام عليكم ورحمة الله وبركاته

يمكنك متابعة السلسلة بالترتيب أو الانتقال مباشرة إلى أي مقال:


المقدمة

مبدأ الـ Interface Segregation هو المبدأ الرابع من مبادئ الـ SOLID
هذا المبدأ يقول لك أنه يجب أن تقوم بتقسيم الـ interface الكبيرة إلى عدة interface صغيرة ومتعددة بدلًا من interface واحد كبير يحتوي على العديد من الدوال والمتغيرات

لأن مشكلة الـ interface أنه يجبرك على استخدام متغيرات وتنفيذ دوال قد لا تريدها
وهذا وارد في الـ interface الكبير الذي يضم العديد من الدوال والمتغيرات فستجد نفسك مجبور على تنفيذ واستخدام أشياء لا تريدها

لذا فالمبدأ يركز على تقسيم الـ interface الكبير إلى عدة interface صغيرة وكل واحدة تركز على شيء معين

تخيل معي أنك قمت بعمل interface يدعى Person وقمت بتعريف بعض المتغيرات والدوال التي يجب أن يحتويها كل object ينتمي لعائلة interface الـ Person
ووضعت فيه متغيرات ودوال مثل name, workplace, salary, getName, getWorkplace, getSalary وغيرها

تخيل أنك تريد استخدام هذا الـ interface في كلاسات مختلفة مثل Employee, Customer, Admin, Student وغيرها
وتجد نفسك مجبرًا على تنفيذ واستخدام الدوال التي لا تحتاجها في كل كلاس
مثل workplace و salary لا تحتاجها في الكلاسات Customer و Student لانهم ليس لديهم مكان عمل او راتب
ولكنك مجبر على تنفيذها لأنها موجودة في الـ interface الـ Person

تلك المشكلة ظهرت معنا في مبدأ الـ Liskov Substitution عندما كان كلاس ClothesProduct مجبر على تنفيذ دالة getExpiredDate برغم أنه لا يحتاجها

لذا فالمبدأ يهتم بتلك المشكلة لكن من ناحية الـ interface بتقسيمه إلى عدة interface صغيرة ليمنع تلك المشكلة ولا يتم اجبار الكلاسات على تنفيذ واستخدام الدوال التي لا تحتاجها

يمكننا أن نقول أن تحقيق مبدأ الـ Interface Segregation يساعد على تحقيق مبدأ الـ Liskov Substitution وتفادي مشاكله إلى حد ما
لكن مبدأ Interface Segregation يركز على تقسيم الـ interface بشكل عام وتجنب الإجبار على استخدام الدوال التي لا تحتاجها

مثال يناقض المبدأ

تخيل معين أننا لدينا interface يدعى IMailData يحتوي على العديد من الأمور التي قد نستخدمها في إرسال أي شيء عبر البريد الإلكتروني

interface IMailData {
  from: string;
  to: string;
  senderEmail: string;
  receiverEmail: string;
  subject: string;
  message: string;
  otp: string;
  otpExpiredAt: Date;
  attachments: string[];

  // ... etc.
}

ثم لنفترض أننا لدينا دوال تستقبل هذا الـ interface مثل sendReport, sendInfo, sendOTP

class EmailService {
  public sendInfo(mailData: IMailData) {
    // I don't need otp or attachments
  }

  public sendReport(mailData: IMailData) {
    // I don't need otp
  }

  public sendOTP(mailData: IMailData) {
    // I don't need attachments
  }

  // ... etc.
}

لاحظ أن كل دالة تستقبل نفس الـ interface وهو يحتوي على العديد من الأمور التي قد لا تحتاجها كل الدوال
لكن بسبب interface الكبير فأنت مجبر على إرسال كل البيانات التي يريدها حتى لو لم تستخدمها داخل الدالة

فسينتهي بك الأمر بكود يبدو كهذا

let emailService = new EmailService();

emailService.sendInfo({
  from: 'Ahmed El-Tabarani';
  to: 'إلى من يهمه الأمر';
  senderEmail: 'eltabaraniahmed@gmail';
  receiverEmail: 'string@gmail.com';
  subject: 'مقالة جديدة';
  message: 'مقالة عن مبادئ يُقال أنها مهمة';
  attachments: null; // i don't need it
  otp: null; // I don't need it
  otpExpiredAt: null; // I don't need it
  // ... etc.
})

لاحظ أننا قمنا بإرسال كامل البيانات التي يطلبها من الـ interface بشكل اجباري
لذا احيانًا قد تجد ترسل null للبيانات التي لا تحتاجها

مثال يوافق المبدأ

الحل بسيط وهو تقسيم الـ interface الكبير إلى عدة interface صغيرة وكل واحدة تركز على شيء معين

interface IBasicMailData {
  from: string;
  to: string;
  senderEmail: string;
  receiverEmail: string;
  subject: string;
  message: string;
}

interface IOTPData extends IBasicMailData {
  otp: string;
  otpExpiredAt: Date;
}

interface IReportMailData extends IBasicMailData {
  attachments: string[];
}

ثم يمكنك استخدام الـ interface التي تحتاجها فقط داخل كل دالة

class EmailService {
  public sendInfo(infoData: IBasicMailData) {
    // now i have all the data i need in IBasicMailData
  }

  public sendReport(reportData: IReportMailData) {
    // now i have all the data i need in IReportMailData
  }

  public sendOTP(otpData: IOTPData) {
    // now i have all the data i need in IOTPData
  }

  // ... etc.
}

let emailService = new EmailService();
emailService.sendInfo({
  from: 'Ahmed El-Tabarani';
  to: 'إلى من يهمه الأمر';
  senderEmail: 'eltabaraniahmed@gmail';
  receiverEmail: 'string@gmail.com';
  subject: 'مقالة جديدة';
  message: 'مقالة عن مبادئ يُقال أنها مهمة';
})

هكذا أصبح لديك القدرة على إرسال البيانات التي تحتاجها فقط داخل كل دالة دون الحاجة لإرسال البيانات الأخرى التي لا تحتاجها
وهذا يعني أنك تقوم بتطبيق مبدأ الـ Interface Segregation بشكل صحيح
وهو كما ترى مبدأ بسيط وجميل يساعدك على إنشاء interface صغيرة تخدمك في جزئية معينة دون أن يتم اجبارنا على استخدام البيانات التي لا نحتاجها

يمكنك أن تتخيل مبدأ الـ Interface Segregation على أنه مبدأ الـ Single Responsibility على مستوى الـ interface

خلاصة مبدأ الـ Interface Segregation

المبدأ ببساطة يركز على تقسيم الـ interface الكبير إلى عدة interface صغيرة وكل واحدة تركز على شيء معين
بمعنى أنك لا تجعل الـ interface يحتوي على العديد من الأمور التي قد لا تحتاجها كل الكلاسات
بحيث لا تجبر من يقوم ببناء الـ interface على استخدام متغيرات أودوال لا يحتاجها

فإذا كان لديك interface يدعى IPerson ويحتوي على العديد من الأمور مثل:

interface IPerson {
  name: string;
  age: number;
  workplace: string;
  salary: number;
  school: string;
  grade: number;
  getSchool(): string;
  getGrade(): number;
  getWorkplace(): string;
  getSalary(): number;
}

هل تعتقد أن كلاسات مثل الـ Student يستطيع بناء IPerson واستخدام كل هذه الأمور؟ بالطبع لا
لأن الـ Student ليس لديه مكان عمل أو راتب ولا يحتاجها فلما يتم اجباره ؟
هنا نحتاج إلى تقسيم IPerson إلى IBasicPerson و IWorkablePerson و IEducablePerson وغيرها

interface IBasicPerson {
  name: string;
  age: number;
}

interface IWorkablePerson {
  workplace: string;
  salary: number;
  getWorkplace(): string;
  getSalary(): number;
}

interface IEducablePerson {
  school: string;
  grade: number;
  getSchool(): string;
  getGrade(): number;
}

ثم تجعل كل كلاس يبني ما يريده

class Student implements IBasicPerson, IEducablePerson {
  public name: string;
  public age: number;
  public school: string;
  public grade: number;

  public getSchool() {
    return this.school;
  }

  public getGrade() {
    return this.grade;
  }
}

class Employee implements IBasicPerson, IWorkablePerson {
  public name: string;
  public age: number;
  public workplace: string;
  public salary: number;

  public getWorkplace() {
    return this.workplace;
  }

  public getSalary() {
    return this.salary;
  }
}

رسالة خاصة

أرسل ملاحظاتك أو رأيك بشكل خاص — لن يظهر للآخرين

التعليقات

شاركنا رأيك في هذه المقالة أو اسأل عن أي شيء يخصها