دايما بنسمع عن حد يقولك الكود بتاعك دا مخالف للمبدأ كذا من مبادئ ال solid بس بييجى فى ذهنك ان هو بيأفور عشان الكود بتاعى شغال اهو بدون مشكلة .. تعالى اقولك انه مش شرط المشكلة تواجهك دلوقتى لكن تأكد انها هتقابلك بعدين زى مثلا ان الكود بتاعك مش Reusability يعنى مش قادر تستخدمه مره واتنين وتلاتة وبقا اسهل ليك انك تكتب نفس الكود تانى بالمطلبات بتاعتك ودا مش صحيح خصوصا لما يبقى اللوجيك مشترك فى نفس الحاجة فى اماكن مختلفة. او Maintainability او Flexability وغيرها من المشاكل .
اولا لازم نعرف ان الى قدم لينا المبادئ دى هو المهندس روبرت مارتن. والى مشهور بلقب العم بوب .. ماعلينا خلينا نشوف ايه هى المبادئ دى.
تعالى كدا اقولك ايه هى مبادئ ال solid:
اولا كلمة solid هى كلمة من خمس احرف وكل حرف بيعبر عن بداية حرف كل مبدأ
1- S: (Single Responsibility Principle (SRP))
2- O: (Open/Closed Principle (OCP))
3- L: (Liskov Substitution Principle (LSP))
4- I: (Interface Segregation Principle (ISP))
5- D: (Dependency Inversion Principle (DIP))
خلينا بقا ناخدهم واحد واحد:
1- Single Responsibility Principle (SRP):
المبدأ دا بيقولك ان المفروض يكون الكلاس له مهمة واحدة بس .. يعنى مسئول عن حاجة واحدة بس زى مثلا ان فيه كلاس بيضيف يوزر بس ملوش علاقة انه يبعتله ميل .. من الاخر ميبقاش له اكتر من سبب عشان اعدل فيه.
خلينا كدا ناخد مثال عن لوجيك انشاء تقارير ونشوفه وهو مخالف المبدأ مره ومره وهو بيطبق المبدأ
المثال المخالف للمبدأ:
class Report {
public function generate() {
}
public function saveToFile() {
}
}
لو لاحظنا هنا هنلاقى ان الكلاس دا له مهمتين الاولى هى انشاء التقرير والتانية هى حفظ التقرير فى ملف وكدا هو مخالف للمبدأ لان الكلاس بالشكل دا له مهمتين واكتر من سبب عشان اعدل فيه ودا غلط والمبدأ بيقولك الكلاس له مهمة واحدة فقط.
المثال الى بيطبق المبدأ:
class Report {
public function generate() {
}
}
class ReportSaver {
public function saveToFile(Report $report) {
}
}
هنا هنلاحظ ان انا بقا عندى اتنين كلاس وكل كلاس له مهمة واحدة وبالشكل دا احنا طبقنا اول مبدأ وهو ال Single Responsibility Principle
2- Open/Closed Principle (OCP):
المبدأ دا بيقولك ان الكود المفروض يبقى مفتوح للإضافة وليس التعديل .. يعنى انا مثلا باحى بدل مااجمع كل نظم الخصم فى كلاس واحد لا انا بعمل interface وبعمل لكل نوع خصم كلاس بي implement ال interface دى .. وبناءا عليه بدل ماكنت عشان اضيف نوع خصم اروح اعدل فى الكلاس الاساسى لا انا بعمل كلاس جديد لنوع الخصم دا واخليه ي implement ال interface دى ويشتغل زى الباقى .. يبقى كدا بضيف بس مش بعدل ودا تقريبا نفس طريقة ال strategy design pattern.
خلينا كدا ناخد مثال عن لوجيك طرق الدفع ونشوفه وهو مخالف المبدأ مره ومره وهو بيطبق المبدأ
المثال المخالف للمبدأ:
class PaymentProcessor {
public function pay($type) {
if ($type == 'paypal') {
} elseif ($type == 'credit_card') {
}
}
}
هنلاحظ هنا انى لو حبيت اضيف طريقة دفع جديدة هدخل اعدل فى الكود ودا مخالف للمبدأ لان المبدأ بيقولك المفروض الكود يبقى مفتوح للاضافة مش التعديل.
المثال الى بيطبق المبدأ:
interface PaymentMethod {
public function pay();
}
class PayPal implements PaymentMethod {
public function pay() {
}
}
class CreditCard implements PaymentMethod {
public function pay() {
}
}
هنا هنلاحظ انى قسمت اللوجيك ل interface وكلاسات بتنفيذ ال interface دا وبالتالى لو حبيت اضيف طريقة دفع جديدة هعمل كلاس جديد واخليه ينفذ هو كمان ال interface دا وبكدا انا طبقت المبدأ لانى بضيف الجديد فقط مش بعدل فى القديم فى كل مره.
3- Liskov Substitution Principle (LSP):
المبدأ دا بيقولك ان المفروض لو عندى كلاس اب وكلاس ابن المفروض لو الاب فيه ميثود مثلا بتحسب المرتب والابن ورث منه مينفش الاتنين يدونى نتائج مختلفة لو استعملت اى واحده فيهم .. بمعنى اصح وجود لوجيك ثابت فى الاب وبيتغير لما يوصل للابن دا غلط والمفروض ميبقاش فى لوجيك فى الاب ويبقى فيه interface بسيط هو الى فيه الميثود دى والابناء يورثوها وكل ابن بطريقته الخاصه.
خلينا كدا ناخد مثال عن لوجيك لخصائص ونشوفه وهو مخالف المبدأ مره ومره وهو بيطبق المبدأ
المثال المخالف للمبدأ:
class Bird {
public function fly() {
echo "Flying";
}
}
class Penguin extends Bird {
public function fly() {
throw new Exception("Penguins can't fly!");
}
}
فى المثال دا هنلاحظ ان انا عندى كلاس اب وفيه خاصية الطيران وورث منه الابن الى هو كلاس البطريق ولقينا هنا ان خاصية الطيران مش مناسبه للبطريق وبالتالى نفذت ميثود مش مناسبه ليا لان جاى فيها خصائص متخصش البطريق ودا طبقا مخالف للمبدأ لانه بيقولك المفروض الميود بتتغير حسب كل ابن ومينفعش نحطها بشكل ثابت فى الاب.
المثال الى بيطبق المبدأ:
interface Bird {
public function eat();
}
class FlyingBird implement Bird {
public function fly();
}
class Sparrow implements FlyingBird {
public function eat() { echo "Eating"; }
public function fly() { echo "Flying"; }
}
class Penguin implements Bird {
public function eat() { echo "Eating"; }
}
هنا ملاحظين انى قسمت اللوجيك لاكتر من interface وحذفنا اللوجيك الثابت الى بيتم وراثته و ودا بي
4- Interface Segregation Principle (ISP):
هنا المبدأ دا بيقولك ان المفروض الكود ميبقاش مجبر على ميثودز هو مش محتاجها اصلا يعنى مثلا ميبقاش عندى intercface فيها كل حاجة زى مثلا crud for user و كمان فيها userNewspaperSubscription لا الصح هنا والمبدأ بيقولك انك تفصل ال interface دا ل interfaces صغيره عشان الكلاس الى محتاجة اللوجيك دا ي implement ال interface دا والى مش محتاجه يبقى خلاص وبكدا مفيش كلاس مجبر ينفذ كود هو مش محتاجه.
خلينا ناخد مثال عن الروبوت والانسان
المثال المخالف للمبدأ:
interface Worker {
public function work();
public function eat();
}
class Robot implements Worker {
public function work() { echo "Working"; }
public function eat() { throw new Exception("Robots don't eat!"); }
}
فى المثال دا هنلاحظ ان انا عندى interface فيها الشغل والاكل وهنا عندى الروبوت مجبر ينفذ ميثود الاكل على الرغم من انه مش محتاجها
المثال الى بيطبق المبدأ:
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
class Robot implements Workable {
public function work() { echo "Working"; }
}
class Human implements Workable, Eatable {
public function work() { echo "Working"; }
public function eat() { echo "Eating"; }
}
هنا هنلاحظ انى بالفعل طبقت المبدأ فى انى خليت فيه اكتر من interface وبالتالى كل كلاس هيحتاجه يروح يعمل implement للinterface دا وبالتالى مبقاش عندى اجبار على تنفيذ ميودز
5- Dependency Inversion Principle (DIP):
المبدأ دا بيقولك مينفعش يبقى فيه high level class بيعتمد على low level class لكن المفروض ان الاتنين بيعتمدوا على abstraction يعنى على abstract class او interface
تعالوا ناخد مثال على الداتا بيزس
المثال المخالف للمبدأ:
class MySQLDatabase {
public function connect() {
// Connect MySQL db
}
}
class UserRepository {
private $db;
public function __construct() {
$this->db = new MySQLDatabase();
}
}
هنا المثال دا مخالف لانه ببساطة بيعتمد على low level class الى هو MySQLDatabase وبالتالى بقا ينفذه هو بنفسه عشان يقدر ينفذ باقى العمليات بتاعته ودا مش صحيح
المثال الى بيطبق المبدأ:
interface Database {
public function connect();
}
class MySQLDatabase implements Database {
public function connect() {
echo "Connected to MySQL";
}
}
class UserRepository {
private $db;
public function __construct(Database $db) {
$this->db = $db;
}
}
هنا هنلاحظ ان انا بقا عند ال low level class و ال high level class بقوا معتمدين على abstraction وطبعا ممكن يعتمد هنا على interface او abstract class وبالتالى ال high level مبقاش بينفذ ال low level class بنفسه ولكن بقا بياخده جاهز من ال construct وبالتالى بقا عندى اتاحة لانى استعمل اى داتا بيز كلاس واغيره بكل سهولة
وكدا يبقى احنا فمهنا مبادئ ال solid مع مثال لكل حالة
متنساش تعمل متابعة على LInkedin 🤓