اصول solid در زبان جاوا اسکریپت

اصول solid در زبان جاوا اسکریپت نوشته شده در   javascript ۱۱ مرداد , ۱۳۹۸ توسط  عباس حسینی

اصول solid اصلول ۵ گانه ای است که به ما کمک میکند که نرم افزاری توسعه دهیم که قابلیت نگهداری و توسعه داشته و همچنین از مشکلاتی زیر ما را در امان نگه میدارد 

  • وقتی به خاطر یک تغییر کوچک در سیستم نیاز باشد کل سیستم را تغییر دهیم
  • وقتی که افزودن یک قابلیت جدید به پرزه بسیار سخت و زمانبر باشد
  • وقتی استفاده مجدد از یک بخش کوچک از پروزه در جای دیگر امکانپذیر نباشد

 

اصل single responsibility principle در جاوا اسکریپت

قائده ای از solid  که در آن تاکید بر این موضوع دارد که هر کلاس نباید بیش از یک مسئولیت داشته باشد زیرا اگر یک کلاس دو وظیفه داشته باشد وابستگی بین آنها ایجاد شده و وقتی بخواهیم تغییری در یکی از آنها ایجاد کنیم بر بقیه عناصر کلاس نیز اثر گذار خواهد بود

به عنوان مثال در اینجا یک کلااس به نام UserDetails داریم که دو وظیفه اعتبار سنجی و تغییر اطلاعات کاربر را به عهده دارد بنابراین  با هر  تغییر در اطلاعات کاربر ، اعتبار سنجی هم به خاطر وابستگی ای که به آن دارد باید تغییر داده شود پس اصل SRP را نقض کرده 


//Bad
class UserDetails {
  constructor(user) {
    this.user = user;
  }
  // task1
  changeSettings(settings) {
    if (this.verify()) { }
  }
  //task2
  verify() {}
}

برای اصلاح آن باید آن را به دو کلاس با وظیفه مجزا تفکیک کنیم


//Good
class UserAuth {
  constructor(user) {
    this.user = user;
  }
  verify() {}
}

class UserSettings {
  constructor(user) {
    this.user = user;
    this.auth = new UserAuth(user);
  }
  changeSettings(settings) {
    if (this.auth.verify()) {}
  }
}

Open/closed principle در جاوا اسکریپت

قائده ای از solid  که در آن تاکید بر این موضوع دارد که هر یک از اجزای سیستم مثل کلاس ، تابع ، کامپوننت و… برای توسعه باز و برای تغییر بسته هستند

مفهوم این جمله آن است که برای افزودن یا تغییر کوچک در سیستم نباید تغییری در کد های دیگر ایجاد شود مثلا فرض کنید سیستم شما انواع گزارش مثلا pdf را تهیه میکند و حال شما قرار است یک گزارش جدید با فرمت ورد به سیستم اضافه کنید اگر این کار مستلزم تغییر در کد های قبلی باشد شما اصل OCP را نقض کرده اید

طراحی شما باید به گونه ای باشد که بدون تغییر در کد های قبلی براحتی بتوانید جزئی که میخواهید را به سیستم اضافه کنید

به مثال زیر توجه کنید در حال حاضر یک کلاس به نام Report داریم که دو نوع گزارش پی دی اف  و ورد را پشتیبانی میکند و در آینده این نیاز به وجود می آید که یک نوع جدیدی از گزارش مثلا xml مورد نیاز است بنابراین برای افزودن آن مجبوریم کد های قبلی را باز کرده و مستقیم تغییر را در آن اعمال کنیم که با این عمل اصل OCP را نقض کرده ایم.


//Bad
class Report {
    constructor (type, data){
        this.type = type;
        this.data = data
    }
    export (){
        if (this.type == 'pdf') {
            //Do Something
        }
        else if ('doc'){
            //Do Something
        }
    }
}


برای اصلاح این کد یک abstract class برای گزارش در نظر گرفته و هر گاه که قرار است نوع جدیدی از گزارش را به کد اضافه کنیم کافیست یک کلاس مجزا برای آن تشکیل داده و متد های به ارث برده از abstract class که در اینجا متد export است را مختص خودش پیاده سازی کنیم

این درواقع همان الگوی طراحی template method است و اگر با آن آشنایی ندارید به لینک زیر مراجعه فرمایید

لینک مطالعه


//Good
//@Abstract class
class Report {
    constructor (data){
        this.data = data
    }
    export (){}
}

class Pdf extends Report {
    export (){
        //Do Something
    }
}
class Doc extends Report {
    export (){
        //Do Something
    }    
}
class Xml extends Report {
    export (){
        //Do Something
    }    
}

اصل Liskov substitution principle در جاوا اسکریپت

قائده ای از solid  که در آن تاکید بر این موضوع دارد که زیر کلاس ها باید بتوانند بدون هیچ تغییر جایگزین کلاس پدر شوند و به دلیل اینکه شخصی به نام باربارا لیسکوو برای اولین بار آن را مطرح کرد به نام آن شخص معروف شد

به کد زیر دقت کنید 


class Bird {
    constructor(name){
        this.name = name
    }
    fly(){

    }
}

class Duck extends Bird{
    fly(){

    }
}

یک کلاس پرنده به نام Brid داریم که در آن یک متد پرواز به نام fly وجود دارد و از طرفی کلاس اردک ازآن به ارث برده است ولی از آنجایی که اردک توانایی پرواز ندارد اصل LSP نقض شده است زیرا کلاس فرزند اردک نمیتواتند جایگزین کلاس پدرش که پرنده است بشود

برای حل این مشکل میتوان برای پرندگاانی که پرواز میکنند و پرواز نمیکنند کلاس مجزا در نظر گرفت



class Bird {
    constructor(name){
        this.name = name
    }
}
class UnFlightFulBird extends Bird{
    constructor(name){
        super(name)
    }
}
class FlightfulBird extends Bird{
    constructor(name){
        super(name)
    }
    fly(){

    }
}
class Duck extends UnFlightFulBird{
}
}

Interface segregation principle در جاوا اسکریپت

قائده ای از solid  که در آن تاکید بر این موضوع دارد که استفاده از رابط تک وظیفه ای بهتر از رابط چند وظیفه ای است

به عبارتی کلاینت ها (کلاس هایی که از کلاس پدر به ارث میبرند) نباید وابسته به متد هایی باشند که ازشون استفاده نمیکنند پس بهتر است به جای داشتن یک interface با متد های زیاد آن را به interface های کوچکتری تقسیم کنیم 

در مثال زیر کلاس Child از Base که یک interface است به ارث برده بنابراین مجبور به پیاده سازی تمام متد های آن است د حالی که فقط به متد شماره دو نیاز دارد ولی باید متد شماره ۱ را نیز پیاده سازی کند چون با خطا مواجه میشود و الگوی IPS را نقض کرده است

//Interface

class Base {
    Method1();
    Method2();
}

class Child extends Base {
    //verride
    Method1() {
        throw new Error('Method1 not implemented');
    }

    //Override Method2() 
    {
       
    }
}

اصل Dependency inversion principle در جاوا اسکریپت

قائده ای از solid  که در آن تاکید بر این موضوع دارد که کلاس یا ماژول های سطح بالا به کلاس ها و ماژول های سطح پایین نباید وابسته باشند بلکه به انتزاعات (interface or abstract class) باید وابسته باشند

مفهوم این جمله این است که وقتی وابستگی یک ماژول فقط به یک interface میتوان آن در جاهای مختلفی استفاده کرد درست مثل یک دوشاخه برق که به جای اتصال مستقیم لامپ به سیم برق میتوان از دوشاخه برای اتصال استفاده کرد بنابراین توسط آن دوشاخه هر چیزی به سیم برق قابل اتصال است

به عنوان مثال به کد زیر توجه کنید


import Logger from './services'

class Api {
    constructor (){
        this.logger = new Logger();
    }
    notify(msg){
        this.logger.log(msg)
    }
}

 

در این کد سرویس Api مستقیما از سرویس Logger یک شی ساخته و از ان داخل متد notify استفاده کرده است ، بنابراین اگر بخواهیم سرویس لاگر را عوض کنیم باید مستقیما در خود کلاس Logger تغییر ایجاد کنیم

برای حل این مشکل باید از یک Interface استفاده کنیم و به طور مستقیم سرویس مورد نظر را استفاده نکنیم تا اگر خواستیم سرویس را عوض کنیم براحتی با تغییر داخل interface امکان پذیر باشد

 


import Logger from './services'

class ILogger {
    constructor (){
        this.logger = new Logger () 
    }
    log(msg) {
        this.logger(msg)
    }
}

class App {
    constructor (){
        this.logger = new ILogger();
    }
}