الكود الاحترافي مش بس كود يشتغل. لازم يكون واضح وسهل الصيانة، قابل للاختبار، وسهل إعادة استخدامه. على مر السنين تطورت مبادئ وممارسات كثيرة لتحقيق هذا الهدف في البرمجة الكائنية.
أشهر هالمبادئ هي الـ SOLID - خمس مبادئ غيّرت طريقة كتابة الكود الكائني وخلته أوضح، أمرن، وأسهل في الصيانة.
أول مبدأ، وهو الـ S في SOLID، يقول إن كل class لازم يكون عنده وظيفة واحدة فقط، ولازم يتغير لسبب واحد فقط.
خذ مثلا class اسمه Book فيه title وpages وtimesRead. المشكلة لما تشوف جواته method اسمها Save.
public class Book {
private String title;
private int pages;
private int timesRead;
public void save() {
// opens connection
// creates statement
// executes statement
}
}
السؤال هون: ليش Save موجودة جوا Book؟ الـ Book هو entity، ما له علاقة بعملية الحفظ بالـ database.
كمان جوا الـ Save نفسها في أكثر من مسؤولية: فتح الـ connection، إنشاء الـ statement، وتنفيذه. هذا خرق للمبدأ.
الحل هو الفصل. Save تروح على class أو service مخصص وظيفته الوحيدة حفظ الـ entries بالـ database. وبالنها يصير عندنا فصل واضح للمسؤوليات.
هالشي يسهل علينا كثير الصيانة، لأنه إذا بدنا نغير الـ database مستقبلا، رح نعرف بالضبط وين نروح.
الـ O في SOLID يقول إن الـ class لازم يكون مفتوح للإضافة، لكن مغلق للتعديل.
اشترك في النشرة البريدية
دروس جديدة، مقالات، وأدوات مباشرة لبريدك.
تخيل عندنا Book فيه categories: Fiction وEducational، وعندنا class ثاني مسؤوليته يحسب الأرباح لكل كتاب.
public class EarningsCalculator {
public double calculate(Book book) {
if (book.getCategory().equals("Fiction")) {
return book.getCopiesSold() * book.getPrice() * 0.8;
} else if (book.getCategory().equals("Educational")) {
return book.getCopiesSold() * book.getPrice() * 1.2;
}
return 0;
}
}المشكلة: كل مرة نضيف category جديدة، لازم نرجع نعدل على هالكلاس ونضيف case جديد. هذا يعني إن الكلاس صار مفتوح للتعديل، وهذا ضد المبدأ.
الحل هو نسأل أول شي: شو الشي المشترك بين هالحالات؟ كلهم بدهم يحسبوا price. فننشئ interface لهذا.
public interface IEarningsCalculator {
double calculateEarnings(Book book);
}ثم نعمل class مستقل لكل category:
public class FictionEarningsCalculator implements IEarningsCalculator {
@Override
public double calculateEarnings(Book book) {
return book.getCopiesSold() * book.getPrice() * 0.8;
}
}
public class EducationalEarningsCalculator implements IEarningsCalculator {
@Override
public double calculateEarnings(Book book) {
return book.getCopiesSold() * book.getPrice() * 1.2;
}
}هلق إذا أضفنا category جديدة، ما منعدل على أي شي موجود. بس ننشئ class جديد يعمل implement للـ IEarningsCalculator ويشتغل لحاله.
الـ L يقول إن الكلاسات المتفرعة لازم تقدر تحل محل الكلاس الأساسي بدون أي مشاكل.
عندنا Book فيه title وprice وcopiesSold وcoverType. المتجر بيبيع كتبا عادية وكمان كتبا إلكترونية.
المشكلة لما تحاول تعمل EBook extends Book:
public class EBook extends Book {
private double fileSize;
@Override
public String getCoverType() {
throw new UnsupportedOperationException("EBooks don't have covers");
}
}الـ EBook ورث getCoverType من Book، بس هالشي ما ينطبق عليه لأنه مش كتاب ملموس. الحل الوحيد هو نعمل throw لـ exception، وهذا بالضبط ضد المبدأ.
تخيل عندك كود يعمل loop على كل الكتب ويستدعي getCoverType:
for (Book book : books) {
System.out.println(book.getCoverType());
}لو مرّ EBook من بين هالكتب، السيستم رح يوقف بـ error. الـ subclass ما قدر يحل محل الـ parent class.
الحل هو استخدام Composition بدل Inheritance. بدل ما نعمل EBook extends Book، ننشئ BookManager يحتوي على الـ properties اللي نحتاجها:
public class BookManager {
private String title;
private double totalEarnings;
public String getTitle() {
return title;
}
}
public class Book {
private BookManager manager;
private String coverType;
public String getCoverType() {
return coverType;
}
}
public class EBook {
private BookManager manager;
private double fileSize;
public double getFileSize() {
return fileSize;
}
}كل class يحدد بنفسه شو الـ methods اللي يحتاجها عن طريق الـ BookManager، بدون ما يرث صفات ما تنطبق عليه.
الـ I يقول إنه من الأفضل يكون عندنا عدة interfaces صغيرة بدل interface واحدة ضخمة.
لو حاولنا نحل مشكلة الـ EBook بـ interface واحدة كبيرة:
public interface BookInterface {
String getTitle();
String getCoverType();
double getFileSize();
}الـ Book العادي ما عنده fileSize، والـ EBook ما عنده coverType. رجعنا لنفس مشكلة الـ throw.
الحل هو تقسيم الـ interface لعدة interfaces أصغر:
public interface Readable {
String getTitle();
}
public interface Downloadable {
double getFileSize();
}| النوع | ينفذ |
|---|---|
Book | Readable فقط |
EBook | Readable وDownloadable |
هلق كل كلاس بيطبق بس الـ interface اللي يلزمه:
public class Book implements Readable {
@Override
public String getTitle() {
return title;
}
}
public class EBook implements Readable, Downloadable {
@Override
public String getTitle() {
return manager.getTitle();
}
@Override
public double getFileSize() {
return fileSize;
}
}ما في throw وما في methods فارغة.
آخر مبدأ، الـ D، يقول إن الكود لازم يعتمد على abstraction، مش على implementation مباشرة.
هالشي طبقناه فعليا بالـ Open/Closed Principle لما عملنا الـ IEarningsCalculator. الـ BookService مش معتمد على FictionEarningsCalculator أو EducationalEarningsCalculator مباشرة، معتمد على الـ interface:
public class BookService {
private IEarningsCalculator earningsCalculator;
public BookService(IEarningsCalculator earningsCalculator) {
this.earningsCalculator = earningsCalculator;
}
public double calculateEarnings(Book book) {
return earningsCalculator.calculateEarnings(book);
}
}الـ implementation تتحدد بالـ runtime عن طريق Dependency Injection:
IEarningsCalculator calculator = new FictionEarningsCalculator();
BookService service = new BookService(calculator);هيك بتقدر تبدل الـ implementation على كيفك - fiction، educational، أو أي service ثاني - بدون ما تلمس الـ BookService نفسه.
| المبدأ | الحرف | المعنى |
|---|---|---|
| Single Responsibility | S | كل class له وظيفة واحدة فقط |
| Open/Closed | O | مفتوح للإضافة، مغلق للتعديل |
| Liskov Substitution | L | الـ subclass يحل محل الـ parent بدون مشاكل |
| Interface Segregation | I | عدة interfaces صغيرة أفضل من واحدة كبيرة |
| Dependency Inversion | D | اعتمد على abstraction مش على implementation |
هالمبادئ الخمس مع بعض بتخلي الكود أمرن، أسهل في الاختبار، وأسهل في الصيانة. ما لازم تطبقهم كلهم من أول يوم، بس كلما كانوا موجودين بذهنك وانت تكتب كودك، بتشوف الفرق مع الوقت.