ဘယ်နယ်ပယ်မဆို စနစ်နဲ့ခြေချတတ်တဲ့အကျင့် ရှိတာကောင်းတယ်။ ဘာလုပ်လုပ် ပိရိပီပြင်တယ်။ အမှားနည်းတယ်။ တစ်ဖက်မှာ ပြီးစလွယ်ဆိုတဲ့ စိတ်အခံရှိတဲ့အပြင် စနစ်မကျ ဖရိုဖရဲဆိုရင် သွားပြီအဲ့မှာ။ မှားတယ်လို့ပြောတာမဟုတ်ဘူး။ နှစ်ခုချင်းယှဥ်လာရင် အတော်ကြီးကွာတယ်။ ဒီတစ်ခါတော့ SOLID principles အကြောင်းပါ။ Object-Oriented Design (OOD) မှာ software structure တွေ တည်ဆောက်တာ မတူကြဘူး။ ဒါပေမယ့် တည်ဆောက်လိုက်တဲ့တစ်ခုစီတိုင်းဟာ ရှိသင့်တဲ့စံနှုန်းတွေနဲ့ ပြည့်မီသင့်တယ်။ Structure ဟာ နားလည်လွယ်ရမယ်။ လိုအပ်ရင် ပြင်ရလွယ်ကူရမယ်။ Scale လုပ်ရတာ အဆင်ပြေရမယ်။ အဲ့ဒီအတွက် Robert C. Martin က principles ငါးခုကို စတင်မိတ်ဆက်ခဲ့တယ်။
1 S - Single Responsibility Principle (SRP)
Class တစ်ခု စတည်ဆောက်ပြီဆိုရင် သူ့ဆီမှာ တာဝန်တစ်ခုတည်းပဲ ရှိရမယ်။ တနည်းအားဖြင့် class တစ်ခုမှာ သူလုပ်ဆောင်ရမယ့်၊ ပြောင်းလဲရမယ့် reason to change ဟာ တစ်ခုတည်းရှိရမယ်။ အဲ့တာကို SRP လို့ခေါ်တယ်။
SRP မသုံးထားရင် ကြုံလာမယ့်ဖြစ်နိုင်ခြေကို နမူနာတွက်ကြည့်ကြမယ်။ သဘောက စာရင်းကိုင်ဌာနတစ်ခုမှာ staff တစ်ယောက်က ငွေအဝင်အထွက်စာရင်းရှင်းရလည်းသူ၊ report တင်ရလည်းသူ၊ client တွေကို email ပို့ရလည်းသူဆိုရင် တာဝန်သုံးခုလုံးကို တစ်ယောက်တည်းဒိုင်ခံ လုပ်နေရတယ်။ Staff ဘက်က တစ်ခုခုနေမကောင်းဖြစ်ခဲ့ရင် အကုန်ရပ်ရမလို ဖြစ်နေတယ်။ အဲ့လိုလွှဲမှားနေတာကို SRP နဲ့ ပြန်တည့်မတ်ပြီး organize ပြန်ချမယ်။ ကိစ္စတစ်ခုစီအတွက် ကိုယ်စီကိုယ်စီဖြစ်အောင် သုံးယောက်ခွဲထားလိုက်တယ်။ အဲ့တော့မှ တစ်ယောက်အဆင်မပြေလည်း ကျန်တဲ့သူတွေက အလုပ်ဆက်လုပ်နိုင်မှာဖြစ်တယ်။
<?php
// Class တစ်ခုထဲမှာ တာဝန်အမျိုးမျိုး ထည့်ထားတာ (SRP မလိုက်နာ)
class BadEmployee {
public function calculateSalary() {
echo "Calculating salary...\n";
}
public function saveToDatabase() {
echo "Saving employee to database...\n";
}
public function generateReport() {
echo "Generating report...\n";
}
public function sendEmail() {
echo "Sending email...\n";
}
}
// SRP (Single Responsibility Principle) ကို လိုက်နာထား
// Employee class က သူ့တာဝန်တစ်ခု (လစာတွက်တာ) ကိုပဲ လုပ်မယ်
class GoodEmployee {
public function calculateSalary() {
echo "Calculating salary...\n";
}
}
// Database အတွက် သီးသန့်တာဝန်ယူမယ့် class
class DatabaseService {
public function saveToDatabase(GoodEmployee $employee) {
echo "Saving employee to database...\n";
}
}
// Report ထုတ်ဖို့ တာဝန်ယူမယ့် class
class ReportService {
public function generateReport(GoodEmployee $employee) {
echo "Generating report...\n";
}
}
// Email ပို့ဖို့ တာဝန်ယူမယ့် class
class EmailService {
public function sendEmail(GoodEmployee $employee) {
echo "Sending email...\n";
}
}
တစ်ခုချင်း ခွဲထုတ်လိုက်တဲ့အတွက် တစ်ခုခုပြင်ချင်ရင် သက်ဆိုင်ရာ service class ကို တန်းပြင်ရုံနဲ့ ပြီးသွားတယ်။
2 O - Open/Closed Principle (OCP)
OCP အရ classes, modules, functions တွေကို extension လုပ်ဖို့အတွက်ကျ ဖွင့်ထားပေးရမယ်။ Modification ထပ်ပြင်ရမယ့်ဟာဆိုရင် ပိတ်ထားရမယ်။ ဆိုလိုတာက ရှိပြီးသား code တွေကို ပြန်ပြင်ရေးရတာမျိုးမဟုတ်ဘဲ ဘယ်ဟာဖြစ်ဖြစ် အသစ်ထပ်ထည့်လို့ရအောင်ပဲ ဒီဇိုင်းဆွဲရမှာ။
Laptop တစ်လုံးမှာ mouse, keyboard, headphone တွေ ချိတ်လို့ရအောင်ဆိုပြီး port သီးသန့်စီ built-in ပါတယ်ဆိုပါစို့။ အဲ့တာကို speaker နဲ့ ထပ်တွဲချင်ရင် ဘယ်လိုလုပ်ကြမလဲ။ Port ပုံစံတွေမတူလို့ ဖြစ်သလိုဇွတ်ထိုးပြီး သုံးဖို့ဆိုတာလည်း မဖြစ်နိုင်ဘူး။ Laptop ကိုပဲ ပြန် modification မွမ်းမံမှုလုပ်မှသာ port အသစ်တစ်ခု ထည့်နိုင်မှာဟုတ်တယ်။ OCP စံနှုန်းနဲ့ မကိုက်ဘူးလို့ မှတ်ရမယ်။ အဲ့လိုအပ်ချက်ကို OCP နဲ့ကိုက်အောင် ဘယ်လိုဖြေရှင်းမလဲ။ Universal USB port ဆိုပြီး လုပ်လိုက်တယ်။ USB အခေါင်းပါတဲ့ ဘယ် device ပဲလာလာ ထည့်လို့ရသွားတယ်။ အဲ့တော့ Laptop ရဲ့ မူလဒီဇိုင်းကို ဘာမှပြင်စရာမလိုဘူး။ Closed for modification ဖြစ်တယ်ပေါ့။ Device အသစ်သစ်တွေလည်း ထပ်ထည့်လို့ရသွားတယ်။ Open for extension ဖြစ်တယ်ပေါ့။
<?php
// OCP (Open/Closed Principle) မလိုက်နာ
class AreaCalculator {
public function calculateArea($shape) {
if ($shape instanceof Rectangle) {
return $shape->width * $shape->height;
} elseif ($shape instanceof Circle) {
return pi() * pow($shape->radius, 2);
}
// Triangle ဒါမှမဟုတ် အခြား shape အသစ်ထပ်ထည့်ချင်ရင်
// ဒီ class ကို ပြန်သွားပြင်ရမယ် -> ဒါဟာ OCP ကို ချိုးဖောက်တာ
return 0;
}
}
// Rectangle class
class Rectangle {
public $width;
public $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
}
// Circle class
class Circle {
public $radius;
public function __construct($radius) {
$this->radius = $radius;
}
}
// OCP (Open/Closed Principle) ကို လိုက်နာ
// Shape ဆိုတဲ့ interface သတ်မှတ်
// shape အသစ်ထပ်ထည့်ချင်ရင် အဲဒီ interface ကို implement လုပ်လိုက်ဖို့ပဲ
interface Shape {
public function calculateArea(): float;
}
// Rectangle - Shape ကို implement လုပ်ထားတယ်
class RectangleOCP implements Shape {
private $width;
private $height;
public function __construct($width, $height) {
$this->width = $width;
$this->height = $height;
}
public function calculateArea(): float {
return $this->width * $this->height;
}
}
// Circle - Shape ကို implement လုပ်ထားတယ်
class CircleOCP implements Shape {
private $radius;
public function __construct($radius) {
$this->radius = $radius;
}
public function calculateArea(): float {
return pi() * pow($this->radius, 2);
}
}
// Triangle - Shape ကို implement လုပ်ထားတယ်
class TriangleOCP implements Shape {
private $base;
private $height;
public function __construct($base, $height) {
$this->base = $base;
$this->height = $height;
}
public function calculateArea(): float {
return 0.5 * $this->base * $this->height;
}
}
// AreaCalculatorOCP က အခြား class တွေကို မပြင်
// Shape ကို implement လုပ်ထားတဲ့ object ကို သုံးတာနဲ့ အဆင်ပြေ
class AreaCalculatorOCP {
public function calculate(Shape $shape): float {
return $shape->calculateArea();
}
}
3 L - Liskov Substitution Principle (LSP)
Parent class နေရာမှာ သူ့ရဲ့ child class ကို အစားထိုးအသုံးပြုနိုင်ရမယ်ပေါ့။ ဒီလိုအစားထိုးလိုက်တဲ့အခါ program ရဲ့ ပုံမှန်လုပ်ဆောင်ချက်တွေသည်လည်း ပျက်စီးမသွားရဘူး။ တနည်းအားဖြင့် program တစ်ခုက base class တစ်ခုကို အသုံးပြုနေတယ်ဆိုရင် သူ့ရဲ့ဆင်းသက်လာတဲ့ derived class မှန်သမျှကို အပြန်အလှန်အစားထိုးသုံးမယ်ဆိုလည်း အလုပ်လုပ်နေရမယ်။ Program ရဲ့ မှန်ကန်မှုကို ထိခိုက်သွားတာမျိုး မဖြစ်ရဘူး။
အိမ်နံရံတစ်ခုပေါ်မှာ လျှပ်စစ် plug ခုံတစ်ခုကို တပ်မယ်ဆိုပါစို့။ အဲဒီ plug outlet က တိကျတဲ့ interface တစ်ခုကိုပေးတယ်။ ဆိုလိုတာက သူ့ဆီ plug တစ်ခုခုထိုးလိုက်ရင် ထိုးတဲ့ကောင်ကို လျှပ်စစ်ဓာတ်အားပေးတာပဲ။ Lamp, phone charger, fan ကြိုက်တဲ့ဟာထိုး လျှပ်စစ်််ဓာတ်ရမှာဖြစ်တယ်။ ပြီးတော့ device တစ်ခုချင်းစီမှာ ဆိုင်ရာဆိုင်ရာ extended function တွေရှိနိုင်တယ်။ Fan မှာ လည်ပတ်နှုန်းကို နံပါတ်နှိပ်ပြီး ပြောင်းနိုင်သလိုပေါ့။ ဒါပေမယ့် ဘာပဲလုပ်လုပ် outlet ရဲ့ base contract ဖြစ်တဲ့ ပင်ရင်းလျှပ်စစ်အားကို သုံးစွဲရတာပဲ။ အဲ့ဒီအချိန်မှာ lamp, fan, phone charger တပြိုင်တည်းသုံးရင်လည်း plug outlet ရဲ့ operation က ပျက်မသွားဘဲ ပုံမှန်ပဲဖြစ်တယ်ဆိုရင် LSP ဖြစ်တယ်ပေါ့။
အဲ့ဒီနေရာမှာ တခြား device တစ်ခုခုကို plug ထိုးလိုက်ပြီး ခလုတ်တစ်ခုခုကို မြှင့်တင်လိုက်လို့ circuit breaker လုံးဝပျက်သွားတဲ့အထိ plug ခုံပါ shock ဖြစ်သွားတာမျိုးဆိုရင် အဲ့ဒီ device ဟာ အစားထိုးသင့်တဲ့ true subtype မဟုတ်တော့ဘဲ base contract ကို ချိုးဖောက်သွားတာ ဖြစ်တယ်။ LSP နဲ့ မကိုက်တော့ဘူးပေါ့။
<?php
// LSP မလိုက်နာ
class Bird {
public function fly() {
echo "Bird is flying...\n";
}
}
class Ostrich extends Bird { // ငှက်ကုလားအုတ်ဟာ ငှက်ပါပဲ၊ ဒါပေမယ့်...
public function fly() {
throw new Exception("Ostriches can't fly!"); // ပျံမရဘူး
}
}
class Test {
public static function makeBirdFly(Bird $bird) {
$bird->fly();
// Ostrich object ထည့်ရင် exception တက်ပြီး program ပျက်နိုင်တယ်
}
}
// LSP လိုက်နာ
class Bird2 {
// ... အခြား bird properties
}
// ပျံနိုင်တဲ့ ငှက်အတွက် class
class FlyingBird extends Bird2 {
public function fly() {
echo "FlyingBird is flying...\n";
}
}
// ပျံမရတဲ့ ငှက်အတွက် class
class NonFlyingBird extends Bird2 {
// fly() method မရှိဘူး
}
// စာကလေး - ပျံနိုင်တယ်
class Sparrow extends FlyingBird {
public function fly() {
echo "Sparrow is flying...\n";
}
}
// ငှက်ကုလားအုတ် - မပျံနိုင်ဘူး
class Ostrich2 extends NonFlyingBird {
// fly() method ကို override လုပ်စရာမလိုဘူး
}
class Test2 {
public static function makeBirdFly(FlyingBird $bird) {
$bird->fly(); // FlyingBird subclass မည်သည့် object ကိုမဆို အားလုံးအဆင်ပြေ
}
}
4 I - Interface Segregation Principle (ISP)
တကယ်တမ်းမှာ မသက်ဆိုင်တဲ့ method တွေကို implement လုပ်ခိုင်းလို့ရအောင်ဆိုပြီး interface တစ်ခုထဲ အတင်းထဲပြီး မတည်ဆောက်ရပါဘူး။ အစုလိုက်အပြုံလိုက်ဖြစ်နေတဲ့ interface တစ်ခုထက်စာရင် သေးငယ်တိကျတဲ့ interface က ပိုထိရောက်တယ်။
စက်ရုံတစ်ခုမှာ လူတစ်ယောက်ကို mechanics ပိုင်း၊ electronic ပိုင်း၊ တခြားအထွေထွေပါ ပြုပြင်နိုင်မယ်လို့ထင်ပြီး တာဝန်ပေးထားတယ်ဆိုပါစို့။ ခက်တာက ဒီလူဟာ ဘယ်တာဝန်ကိုမှ ကျွမ်းကျင်စွာ မလုပ်နိုင်ဘူး။ တကယ်တမ်းမလုပ်နိုင်ဘဲ တာဝန်တွေအများကြီးနဲ့ ဝန်ပိနေတယ်လို့ ပြောလို့ရတယ်။ ခရီးမရောက်ဘူး။ ISP မလိုက်နာဘူး။
Mechanic, electrician, plumber ဆိုပြီး ကျွမ်းကျင်မှုအလိုက် သီးသန့်ခွဲလိုက်တယ်။ အဲ့လိုခွဲလိုက်ရင် mechanic ကို မီးကြိုးတွေ လိုက်လဲခိုင်းစရာမလိုဘူး။ သူ့တာဝန်က စက်ကိရိယာတွေကိုပဲ အာရုံစိုက်ရမယ် ဖြစ်သွားပြီ။ ရလာမယ့်အားသက်ချက်က တစ်ဦးချင်းစီဟာ ကိုယ်စီကျွမ်းကျင်ရာမှာ focus စူးစိုက်နိုင်သွားမယ်။ အလုပ်တွင်မယ်။
<?php
// ISP မလိုက်နာတဲ့ Interface
interface Worker {
public function work();
public function eat();
public function code(); // Developer အတွက်
public function design(); // Designer အတွက်
public function test(); // Tester အတွက်
}
// Manager ဆိုတဲ့ Class က work() နဲ့ eat() ကိုပဲ implement လုပ်ချင်တယ်။
// ဒါပေမယ့် code(), design(), test() ဆိုတဲ့ မလိုအပ်တဲ့ method တွေကိုပါ implement လုပ်ခိုင်းနေတယ်။
class ManagerBad implements Worker {
public function work() {
echo "Managing team...\n";
}
public function eat() {
echo "Manager is eating lunch...\n";
}
public function code() {
// မလိုအပ်ပေမယ့် implement မလုပ်မဖြစ် ဖြစ်နေ
throw new Exception("Manager can't code!");
}
public function design() {
throw new Exception("Manager can't design!");
}
public function test() {
throw new Exception("Manager can't test!");
}
}
// ISP လိုက်နာတဲ့ Interface
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
interface Codable {
public function code();
}
interface Designable {
public function design();
}
interface Testable {
public function test();
}
// Class တစ်ခုချင်းစီက သူတို့လိုအပ်တဲ့ Interface တွေကိုပဲ implement လုပ်ရုံပါပဲ။
class Developer implements Workable, Eatable, Codable {
public function work() {
echo "Developer is working on code...\n";
}
public function eat() {
echo "Developer is eating pizza...\n";
}
public function code() {
echo "Developer is coding in PHP...\n";
}
// design() နဲ့ test() ကို implement လုပ်စရာမလိုတော့ဘူး။
}
class Manager implements Workable, Eatable {
public function work() {
echo "Manager is planning tasks...\n";
}
public function eat() {
echo "Manager is eating lunch...\n";
}
// Manager အတွက် မလိုအပ်တဲ့ method တွေ implement မလုပ်စရာမလိုတော့ဘူး။
}
class Tester implements Workable, Eatable, Testable {
public function work() {
echo "Tester is working on test cases...\n";
}
public function eat() {
echo "Tester is eating noodles...\n";
}
public function test() {
echo "Tester is testing the application...\n";
}
}
5 D - Dependency Inversion Principle (DIP)
High-level modules တွေဟာ low-level modules တွေပေါ်မှာ depend မှီခိုနေတာမျိုး ဖြစ်မနေသင့်ပါ။ နှစ်ခုလုံးဟာ abstractions ဖြစ်တဲ့ interfaces တွေပေါ်မှာပဲ depend ဖြစ်သင့်ပါတယ်တဲ့။ Abstractions ကလည်းထပ်ပြီး details ပေါ်မှီခိုနေတာမျိုး မဖြစ်ရပါဘူးတဲ့။ Details တွေကသာ abstractions ပေါ် depend လုပ်ရပါမယ်။
Remote control တစ်ခုဟာ Zoe TV ပါတဲ့ အတွင်းပိုင်း circuit အလုပ်လုပ်ပုံကို အသေးစိတ်ကို နားလည်တယ်။ ဒီ circuit ကိုပဲ ထိန်းချုပ်ဖို့အတွက် ဒီဇိုင်းဆွဲထားတယ်။ အဲ့ remote က Zoe TV အတွက်ပဲ အလုပ်လုပ်မယ်။ မတူညီတဲ့အမျိုးအစား Hit TV ကိုလဲပြီး ကြည့်မယ်ဆိုရင် remote ပါ အသစ်ဝယ်ရမယ်။ ကျော်ကြည့်မယ်ဆိုတာတွေ မရတော့ဘူးပေါ့။ DIP မလိုက်နာဘူး ပြောလို့ရတယ်။
အဲ့ဒါအစား universal remote control လက်ထဲရှိတယ် ဆိုပါစို့။ ဒီ remote ဟာ TV တိုင်းရဲ့ အတွင်းပိုင်း circuit အသေးစိတ်ကို မသိဘူးထားအုံး။ ဒါပေမယ့် TV အများစုမှာ ရှိသင့်တဲ့ abstract interface တွေဖြစ်တဲ့ powerOn(), powerOff(), changeChannel(), adjustVolume() တွေကို remote ကသိနေတယ်။ ဒီတော့ TV ဟာ ဘယ် brand ပဲဖြစ်နေပါစေ။ ဒီ interface ကို implement လုပ်ထားရင် remote က ထိန်းချုပ်လို့ရနေပြီ။ High-level module ဖြစ်တဲ့ remote က low-level module TV circuit ကို တိုက်ရိုက်မှီခိုနေတာမဟုတ်တော့ဘူး။ Abstraction ဖြစ်တဲ့ TV interface ကို depend လုပ်တာဖြစ်သွားပြီ။
<?php
// DIP မလိုက်နာ
class LightBulb { // Low-level module
public function turnOn() {
echo "LightBulb is ON\n";
}
public function turnOff() {
echo "LightBulb is OFF\n";
}
}
class SwitchBad { // High-level module
private $bulb; // Switch က LightBulb ကို တိုက်ရိုက်မှီခိုနေတယ်။
public function __construct(LightBulb $bulb) {
$this->bulb = $bulb;
}
public function operate($on) {
if ($on) {
$this->bulb->turnOn();
} else {
$this->bulb->turnOff();
}
}
}
// Bulb အစား Fan ကို ထိန်းချင်ရင် SwitchBad class ကို ပြန်ပြင်ဖို့လိုမယ်။
// DIP လိုက်နာ
interface Switchable { // Abstraction
public function turnOn();
public function turnOff();
}
class LightBulbGood implements Switchable { // Low-level module (Detail)
public function turnOn() {
echo "LightBulb is ON\n";
}
public function turnOff() {
echo "LightBulb is OFF\n";
}
}
class Fan implements Switchable { // Low-level module အသစ် (Detail)
public function turnOn() {
echo "Fan is spinning...\n";
}
public function turnOff() {
echo "Fan stopped.\n";
}
}
class SwitchGood { // High-level module
private $device; // Interface ကိုမှီခိုနေတယ်
public function __construct(Switchable $device) {
$this->device = $device;
}
public function operate($on) {
if ($on) {
$this->device->turnOn();
} else {
$this->device->turnOff();
}
}
}
အခုဆိုရင် Switch class ကို ဘာမှမပြင်ရပဲ lightBulb, fan, သို့မဟုတ် Switchable interface ကို implement လုပ်ထားတဲ့ ဘယ် device ကိုမဆို ထိန်းချုပ်လို့ရပြီ။
ဖောက်သည်က ဒီနေ့မှာတော့ ဒီလိုလိုချင်တယ်။ တစ်လကြာရင် စိတ်ပြောင်းသွားလို့ တခြားဟာတောင်းဆိုနိုင်တယ်။ SOLID Principles နဲ့ ရေးထားတဲ့ code မှန်ရင် ဒီလိုပြောင်းလဲမှုတွေကို လက်ခံနိုင်စွမ်း adaptable မြင့်မားတယ်။ ရှိပြီးသား code တွေကို ကြောက်ခမန်းလိလိ ပြန်ပြင်ရမယ့်အစား code အသစ်ကို ခွဲရေးရုံနဲ့ ပြီးသွားပါတယ်။ SOLID Principles ကို အစောပိုင်းမှာတော့ လေ့လာလိုက်စားရတာ အချိန်ပိုပေးရနိုင်တယ်။ ဒါပေမယ့် ရေရှည်မှာတော့ ရေးထားတဲ့ codebase ကို maintain ရတာလွယ်မယ်။ Modify လုပ်တာ scale လုပ်တာ အဆင်ပြေမယ်။ ဒါသည်လည်း ကောင်းမွန်တဲ့ ရင်းနှီးမြှုပ်နှံမှုတစ်ခုပဲဖြစ်တယ်။ အပေါ်ကဟာတွေ တောက်လျှောက်ဖတ်လာခဲ့ပြီဆိုတော့ software development အတွက် SOLID Principles ဟာ ဘယ်လောက်အရေးပါမှန်း သဘောပေါက်ခဲ့ပါပြီ။ Code examples တွေ ပါသွားတဲ့အတွက် အတော်လေး ရှည်ခဲ့ပါတယ်။
Top comments (0)