اصول شی گرایی در جاوا اسکریپت

اصول شی گرایی در جاوا اسکریپت نوشته شده در   javascript ۴ دی , ۱۳۹۷ توسط  عباس حسینی

object oriented یا شی گرایی برگرفته از تفکری است که میگه همه چیز را میتوان در دنیای واقعی در قالب اشیایی دید که شامل یک سری خصوصیات و رفتار میباشند و باهم در ارتباط تنگاتنگ هستند.
این تفکر به زبان های برنامه نویسی هم ورود پیدا کرد و باعث به وجود آمدن object oriented programming یا برنامه نویسی شی گرا شد که ۴ اصل مهم آن را مورد بررسی قرار میدهیم .

۴ اصل شی گرایی

abstraction در جاوا اسکریپت

abstraction در فارسی به معنی انتزاعی یعنی چیزی که قابل لمس نمیباشد ، به عنوان مثال وقتی میگوییم کلاسی به نام Animal داریم تنها توصیفی از یک حیوان ارائه میدهیم که شامل خصوصیات و رفتار آن میباشد بنابراین کار کردن با یک شی abstract بسیار راحت تر میباشد زیرا جزئیات کمتری دارد

حال که تعریفی از مفهوم asbtraction ارائه شد این سوال مطرح میشود که چگونه یک  abstract class تعریف کنیم و فرق abstract class با abstract چیست ؟
abstract class کلاسی است که یک یا چند abstract method دارد و abstract method متدی است که هیچ پیاده سازی ای ندارد و مفهوم abstract را در خود گنجانده است
استفاده از abstract class به عنوان یک واسط با متد ها و ویژگی های کمتر راحت تر است و تغییر روی آن ، روی پروژه تغییر نمیگذارید

از آنجاییکه جاوا اسکریپت از abstract class پشتیبانی نمیکند میتوانیم آن را پیاده سازی کنیم , قبل از این کار باید به دو نکته توجه کنیم :

  1. abstract class کلاسی است که نمیتواند instantiate بشود
  2. اگر کلاسی از آن به ارث ببرد، متد هایی که داخل آن است را باید حتما پیاده سازی کند

برای فهم بهتر آن به کد زیر توجه کنید


//Abstract Class
class Animal {
    constructor() {
        if (this.constructor === Animal) {
            throw new Error(
              `Abstract class "Animal" 
               cannot be instantiated directly.`
            ); 
        }
    }
    fly () {
        throw new Error(
          `You have to implement
            the method fly.`
        );
    }
}

در اینجا ما یک کلاس انتزاعی (abstract class) به نام Animal داریم که مستقیم نمیتوانیم از روی آن شی بسازیم زیرا داخل constructor آن از انجام این کار جلوگیری میشود حال اگر یک کلاسی از این کلاس انتزاعی Animal به ارث ببرد و بخواهد از متد fly استفاده کند چه اتفاقی میفتد ؟


class Dog extends Animal {
 
}
var instance = new Dog()
instance.fly()

در این کلاس Dog از Animal به ارث برده و اگر از Dog یک شی بسازیم و متد fly را با آن صدا بزنیم با خطا مواجه میشویم زیرا fly یک abstract method است و باید آن را مجددا پیاده سازی یا override کند پس باید به شکل زیر باشد


class Dog extends Animal {

 fly (){
   console.log('dog is flying ...')
 } 
}

var instance = new Dog();
instance.fly()

inheritance در جاوا اسکریپت

inheritance به معنای ارث بری است و یعنی اینکه یک کلاس رفتار و صفات کلاسی دیگر را به ارث ببرد و بهترین راه برای استفاده مجدد (reusability) از کد های قبلی است

به عنوان مثال فرض کنید که یک کلاس Car و یک کلاس Benz داریم که از کلاس Car به ارث میبرد


class Car {
    constructor (){
        this.wheels = 4;
        this.doors = 4;
    }
}
class Benz extends Car {
    constructor (){
        this.color = 'red'
    }
}


var BENZ_CAR = new Benz();
console.log(BENZ_CAR)
/*
{
  color: "red",
  doors: 4,
  wheels: 4
}
*/

در زبان جاوا اسکریپت عملا چیزی به نام کلاس نداریم، بلکه این آبجکت ها هستند که از یکدیگر به ارث میبرند که به آن prototypical inheritance میگویند و این کار از طریق لینک شدن دو آبجکت به یکدیگر اتفاق میفتد

برای فهم کامل prototypical inheritance پیشنهاد میکنم ویدئوی زیر را بطور کامل مشاهده کنید

encapsulation در جاوا اسکریپت

encapsulation به معنای کپسوله کردن است و این یعنی اینکه صفت و رفتار یک کلاس را از دید بقیه مخفی کنیم

فرض کنید که یک mp3 palayer داریم و شما از interface های ان استفاده میکنید که شامل پخش و توقف میباشد، در هنگام استفاده شما هیچی از نحوه اجرای آنها نمیدانید و از دید شما مخفی است

در زبان های برنامه نویسی پشتیبانی کامل تری نسبت به oop دارند تعیین سطح دسترسی از طریق سه کلمه کلیدی public ، privat و protected انجام میپذیرد که هر کدام را به طور جداگانه توضیج میدهم

  1. public: متد هایی که تمام کلاس ها به آن دسترسی دارند
  2. private: متد های private فقط داخل خود همان کلاس قابل دسترسی است
  3. protected : متد های protected علاوه بر عناصر داخلی خود کلاس هر کلاسی که از آن به ارث ببرد نیز به آن دسترسی دارد

اما در جاوا اسکریپت چنین امکانی وجود ندارد و تمام متد ها و پروپرتی ها از نوع public  هستند اما با روش هایی میتوان آنها را private کرد
مثلا در کد زیر کلاس Animal به ازای هر بار که یک شی جدید از آن ساخته میشود مقدار داده ای name را کپسوله میکند یعنی دو شی ساخته شده A و B هر کدام نام مختص به خودشان را دارند


class Animal {
    constructor (name) {
        this.name = name
    }
}

var A = new Animal ('cat');
var B = new Animal ('dog');
console.log(A.name) //cat
console.log(B.name) //dog

اما این کپسوله سازی ای که در جاوا اسکریپت با کلمه کلیدی class انجام میشود از الگوی IFFE بهره میبرد یعنی اگر بخواهید کد آن را تبدیل به حالت بدون class کنیم به شکل زیر میباشد


var Animal = /** @class */ (function () {
    function Animal(name) {
        this.name = name;
    }
    return Animal;
}());

اگر با IFFE آشنایی ندارید مطلب زیر را مطالعه کنید

لینک مطالعه

برای درک بهتر مفهوم encapsulation در جاوا اسکریپت پیشنهاد میکنم ویدئوی زیر را تماشا کنید

polymarphism در جاوا اسکریپت

polymorphism به معنی چند ریختی و در مفهوم اشاره به این موضوع دارد که شی میتواند در چندین قالب مختلف ظاهر شود یا به عبارتی یک interface میتواند بین چند کلاس مشترک باشد و همه آنها همان یک interface را پیاده سازی کنند

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

پس حال که بحث اینترفیس مطرح شد بهتر است درباره آن بدانید و برای این کار به لینک زیر مراجعه کنید

لینک مطالعه

در زبان های پیشرفته معمولا polymorphism به سه روش پیاده میشوند
overriding : یعنی دو متد هم نام با پارامتر های ورودی متفاوت (متفاوت در تعداد یا نوع پارامتر)داشته باشیم اگر به کد سی شارپ زیر توجه کنید دو متد هم نام با پارامتر های متفاوت داریم که یکی از نوع int و دیگری string میباشد


public int CountItems(int x) {
    return x.ToString().Length;
}
public int CountItems(string x) {
    return x.Length;
}

parametric polymorphism : یعنی اینکه یک متد این اجازه رو داشته باشد که با هر نوع ورودی پارامتر به درستی کار کند , که در زبان سی شارپ با عنوان generic type شناخته میشوند


public class Stack {
    private T[] items;
    private int count;
    public void Push(T item) { ... }
    public T Pop() { ... }
}

در کد بالا یک کلاس generic با نام Stack داریم و هر جا که از T استفاده شده یعنی نوع آن generic است و نکته ای که وجود دارد وقتیکه داده ورودی یک کلاس رشته باشد باید برای همه عناصر داخلی که از نوع generic هستند هم رشته باشد
در زبان جاوا اسکریپت به خاطر نوع داده ای داینامیک به خوبی از این خاصیت پشتیبانی میکند پس کلاس Stack را در جاوا اسکریپت نیز میتوانیم پیاده سازی کنیم


function Stack()
{
 this.stack = [];
 this.pop = function(){
  return this.stack.pop();
 }
 this.push = function(item){
  this.stack.push(item);
 }
}

subtype polymorphism : یعنی وقتی از یک آبجکت استفاده میکنیم از آبجکتی که از آن به ارث برده نیز میتوانیم استفاده کنیم


public class Person {
 public string Name {get; set;}
 public string SurName {get; set;}
}
public class Programmer:Person {
    public String KnownLanguage {get; set;}
}
public void WriteFullName(Person p) {
 Console.WriteLine(p.Name + " " + p.SurName);
}
var a = new Person();
a.Name = "John";
a.SurName = "Smith";
var b = new Programmer();
b.Name = "Mario";
b.SurName = "Rossi";
b.KnownLanguage = "C#";

WriteFullName(a);    //result: John Smith
WriteFullName(b);    //result: Mario Rossi

در کد بالا که به زبان سی شارپ است یک کلاس به نام Person داریم که Programmer از آن به ارث برده است و یک متد به نام WriteFullName داریم که ورودی ان یک آبجکت از نوع Person میباشد
همانطور که میبینید داخل متد WriteFullName میتوان ورودی از نوع Programmer هم استفاده کرد چون از Person به ارث برده است و این به خاطر قابلیت polymorphism است
این قائده در جاوا اسکریپت نیز قابل پیاده سازی است


function Person() {
this.name = "";
this.surname = "";
}
function Programmer() {
    this.knownLanguage = "";
}
Programmer.prototype = new Person();
function writeFullName(p) {
    console.log(p.name + " " + p.surname);
}
var a = new Person();
a.name = "John";
a.surname = "Smith";
var b = new Programmer();
b.name = "Mario";
b.surname = "Rossi";
b.knownLanguage = "JavaScript";
writeFullName(a);    //result: John Smith
writeFullName(b);    //result: Mario Rossi