THE S.O.L.I.D Principles

THE S.O.L.I.D Principles

software တွေတည်ဆောက်ရာမှာ ရေရှည်ထိန်းသိမ်းဖို့ လွယ်စေရန် လိုက်နာသင့်တဲ့ principle 5 ခုအကြောင်းပါ

S.O.L.I.D principle တွေကို Classes, Functions, methods, modules တွေမှာလည်း apply လို့ရပါတယ်။ ဒီ post မှာတော့ရှင်းလင်းရလွယ်ကူအောင် class ကိုနမူနာထားပြီးရှင်းပြသွားပါမယ်

S - Single Responsibility

Class တစ်ခုမှာ တာ၀န်တစ်ခုပဲရှိသင့်ပါတယ်။

ဥပမာ: ကျနော်တို့ လစဥ်ရောင်းရငွေတွေကိုတွက်ပြီး report တစ်ခု print ထုတ်ပေးရမယ်ဆိုပါစို့။ ရောင်းရငွေကိုတွက်ဖို့က class တစ်ခု၊ report ကိုထုတ်ဖို့က class တစ်ခု သပ်သပ်ဆီရှိသင့်ပါတယ်။ သပ်သပ်စီမရှိခဲ့ရင် ရောင်းရငွေကိုတွက်တဲ့ code တွေကို ပြင်တဲ့အခါမှာ မလိုအပ်ပဲ print ထုတ်တဲ့အပိုင်းမှာပါ bugs တွေတက်လာတာဖြစ်ကောင်းဖြစ်နိုင်ပါတယ်။

ရည်ရွယ်ချက်: ကိုယ်က class တစ်ခုကို ပြင်လိုက်လို့အဲ့ဒီ class က ပြသနာတွေတက်လာရင်တောာင် တခြားသူနဲ့မဆိုင်တဲ့ အပိုင်း တွေကို မထိခိုက်စေဖို့ရည်ရွယ်ပါတယ်။

O - Open-Closed

Class တစ်ခုကို တိုးချဲ့ပြီးရေးလို့ရသင့်တယ်။ ရှိပြီးသားတွေကို ပြင်ဆင်လို့မရသင့်ဘူး။ wiki က မူရင်း english လိုက အဓိပ္ပယ်ပြည့်စုံပါတယ်။ တချက်ကြည့်ကြည့်ပါ။ "*software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"*လို့ရေးးထားပါတယ်

ဥပမာ: class တစ်ခုက အဖြူရောင်နောက်ခံနဲ့ print ထုတ်နိုင်တယ်။ အဲ့တာကို အပြာရောင်နောက်ခံနဲ့လည်းထုတ်ချင်တယ်။ ဒါဆို class က အဖြူရောင်နောက်ခံနဲ့လည်းထုတ်လို့ရတယ်, အပြာရောင်နောက်ခံနဲ့လည်းထုတ်လို့ရတယ်(extension) ဆိုတာ ဖြစ်သင့်ပါတယ်။ print ထုတ်ရင်အပြာရောင်နဲ့ပဲထွက်တော့တယ်(modification) ဆိုတာမဖြစ်သင့်ပါဘူး။

ရည်ရွယ်ချက်: class တစ်ခုကို လုပ်နိုင်တာတွေပိုတိုးလာအောင်လုပ်မယ်။ ဒါပေမယ့် အရင်ထဲကလုပ်နိုင်ပြီးသားတွေကိုလည်းမထိခိုက်စေရန်ရည်ရွယ်ပါတယ်။ ဒါဆို အဲ့ဒီ class ကို ယူသုံးထားတဲ့ ဘယ်နေရာမှာမှ bug မတက်တော့ပါဘူး။

L - Liskov Substitution

Dog class က Pet class ရဲ့ child ဖြစ်မယ်ဆိုရင် Pet class ကိုသုံးပြီးတည်ဆောက်ထားတဲ့ objects တွေကို Dog class နဲ့ဘာပြသနာမှမရှိပဲအစားထိုးလို့ရနိုင်သင့်တယ်။

ဥပမာ: Pet class က move ဆိုတဲ့အလုပ်လုပ်နိုင်တယ်။ ဒါဆို Dog class ကို move ခိုင်းရင် walk နိုင်တယ်ဆိုတာဖြစ်သင့်တယ်(parent ကလုပ်နိုင်တဲ့အလုပ်ကို child ကလည်းလုပ်နိုင်)။ Dog class ကို move ခိုင်းရင် move တော့မလုပ်နိုင်ဘူး eat လိုက်မယ်ဆိုတာမဖြစ်သင့်ဘူး(parent ကလုပ်နိုင်တဲ့အလုပ်ကို child ကမလုပ်နိုင်တော့ဘူး။ behaviour ပြောင်းသွားပြီ)။

ရည်ရွယ်ချက်: parent class ရော child class ရောကို consistency ရှိဖို့ရည်ရွယ်တယ်။ parent class ကိုသုံးရမယ့်ပုံကတစ်မျိုး child class ကိုသုံးရမယ့်ပုံကတစ်မျိုးမဖြစ်အောင်ကာကွယ်ဖို့ရည်ရွယ်တယ်။

I - Interface Segregation

class တစ်ခုမှာ သူ့ရဲ့ ရည်ရွယ်ချက် ပြီးမြောက်အောင်လုပ်ဖို့လိုတဲ့ actions တွေပဲရှိသင့်တယ်။ မလိုအပ်တဲ့ action တွေမရှိသင့်ဘူး။ မလိုအပ်တဲ့ action တွေပါနေရင် အပြီးဖျက်ပစ်ရင် ဖျက်ပစ်၊ မဖျက်ပစ်ရင် တခြားတစ်နေရာကိုရွေ့ပစ်ရမယ်။

ဥပမာ: အောက်က code ကိုကြည့်ကြည့်ပါ။

interface UserInterface {
    public function buyProduct();
    public function editProduct();
    public function addProduct();
}

အဲ့ဒီမှာဆို UserInterface မှာ method 3 ခုပါတယ်။ buyProduct က customer တွေအတွက်လိုအပ်တဲ့ action ဖြစ်ပြီး editProduct, addProduct က admin တွေအတွက်လိုအပ်တဲ့ action တွေဖြစ်တယ်။

အဲ့ဒီ interface ကို Customer class ရော Admin class ရောက implement ပြီဆိုပါစို့။ Customer ကလည်း editProduct, addProduct အတွက် method တွေရေးပေးရတော့မယ်။ Admin ကလည်း buyProduct အတွက် method ရေးပေးရတော့မယ်။ အဲ့လိုမဖြစ်သင့်ဘူး။ အဲ့ဒီအစား

interface CustomerInterface {
    public function buyProduct();
}

interface AdminInterface {
    public function editProduct();
    public function addProduct();
}

ဆိုပြီး interface ၂ ခုခွဲ၊ သက်ဆိုင်ရာ interface ကိုပဲ implement တာဖြစ်သင့်တယ်။

ရည်ရွယ်ချက်: refactor, change, extend လုပ်တဲ့အချိန်မှာ လွယ်ကူစေဖို့ရည်ရွယ်ပါတယ်။

D - Dependency Inversion

High level class တစ်ခုက low level class ရဲ့ implementation အပေါ်မမှီခိုရဘူး။ high level class ရော low level class ရောက ဘုံ abstraction တစ်ခုအပေါ်ပဲမှီခိုရမယ်။

ဥပမာ: Db class ရယ် User class ရယ်ရှိတယ်ဆိုပါစို့။ User class က high level class ဖြစ်လိမ့်မယ်။ Db class က low level class ဖြစ်လိမ့်မယ်။ (Database နဲ့ချိတ်ဆက်အလုပ်လုပ်တာ၊ disk နဲ့အလုပ်လုပ်တာ၊ network နဲ့အလုပ်လုပ်တာ အစရှိတာတွေကို low level class တွေလို့ခေါ်ကြလေ့ရှိပါတယ်)

class Db 
{
    public function connect()
    {
        //action to connect to db
    }
}

class User
{
    protected $db;
    public function __construct(Db $db)
    {
        $this->db = $db;
    }
}
$db = new Db();
$user = new User($db);

အထက်ပါ ရေးနည်းဟာဆိုရင် Dependency Inversion Principle ကိုမလိုက်နာပါဘူး။ User class က Db class ရဲ့ implementation အပေါ်တိုက်ရိုက်မှီခိုနေပါတယ်။
User class က Db class implementation ကိုသိဖို့လိုလား?
ဒါမှမဟုတ် Db နဲ့ချိတ်ဆက်စရာ နည်းတစ်ခုရှိတယ်ဆိုတာကိုပဲသိရင်ရပြီလား?
Dependency Inversion မှာဆိုရင် Db နဲ့ချိတ်ဆက်စရာနည်းတစ်ခုရှိတယ်ဆိုတာကို သိရင်ရပြီလို့ သတ်မှတ်ပါတယ်။

interface DbInterface 
{
    public function connect();
}

class LegacyDb implements DbInterface 
{
    public function connect()
    {
        // code to connect to db
    }
}

class ModernDb implements DbInterface 
{
    public function connect()
    {
        // code to connect to db
    }
}

class User
{
    protected $db;

    public function __construct(DbInterface $db)
    {
        $this->db = $db;
    }
}

// Decide which db system to use
$db = new ModernDb();
// Or $db = new LegacyDb(); 

// Create a user object with the chosen db system 
$user = new User($db);

အထက်ပါရေးနည်းမှာဆို User က Db class အပေါ်မှာတိုက်ရိုက်မမှီခိုတော့ပါဘူး။ Db နဲ့ပတ်သက်တဲ့ classes တွေရော User ရောက ဘုံ abstraction အပေါ်ပဲမှီခိုသွားပါပြီ။ လိုတဲ့ db system ကိုယူသုံးလို့ရပါပြီ။ အခုဆိုရင် High-level code တွေက သူသုံးထားတဲ့ low-level code တွေရဲ့ implementation အပေါ် မမှီခိုတော့ပါဘူး။

ရည်ရွယ်ချက်: ကျနော်တို့ High level code တွေကို ထိစရာမလိုတော့ပဲ low level implementation တွေကိုကြိုက်သလိုပြောင်းလို့ရဖို့ရည်ရွယ်ပါတယ်။

Summary

အခုဆိုရင်ကျနော်တို့ S.O.L.I.D အကြောင်း အခြေခံလေ့လာပြီးပါပြီ။ အချိန်ပေးပြီးဖတ်ပေးတဲ့အတွက်ကျေးဇူးတင်ပါတယ်။ အကြံပေးချင်တာတွေ comment ပေးသွားဖို့လည်း request လုပ်ပါတယ်ခင်ဗျာ။

References:

https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898

https://en.wikipedia.org/wiki/SOLID