برنامه نویسی شی گرا (OOP) تو جاوااسکریپت/نود.جیاس
بخوای یا نخوای، قبول بکنی یا نکنی OOP یه تغییر بزرگ توی دنیای توسعه نرمافزار بود و همه زبان ها رو (حتی اونایی که این پارادایم رو نداشتن) تحت تاثیر قرار داد.
یه نکته مهم توی جاوااسکریپت وجود داره که در جهت مخالف OOP هست اونم encapsulation هست.
کلاس ها
قانون encapsulation
کلاس تعریف کننده نوع دسترسی به دیتا (private یا public بودن) و نوع رفتار (method ها) اون هست. instance یه کلاس توی برنامه آبجکت خوانده میشه. یکی از اصول OOP همین encapsulation به معنی مخفی کردن دیتا های کلاس هست.
فرض کن Elon Musk یه سطل زباله ساخته که ۳ تا ویژگی داره: خالی شدن همه سطل زباله یا یک کلید، گرفتن آشغالی با زدن یه دکمه، یه دکمه جهت نشون دادن وضعیت سطل زباله. حالا بیاید همچین کلاسی رو بنویسیم:
123456class TrashCan { constructor() { this.items = [] }; throwAway(item) { this.items = [...this.items, item] }; clean() { this.items = [] }; isEmpty() { this.items.length === 0 }; };
حالا بیاید یه instance از اون کلاس بسازیم و یکمی با سطل آشغال Elon Musk ور بریم ببینیم خراب شدنی هست یا نه؟
123456789let elonTrashCan = new TrashCan(); elonTrashCan.throwAway('paper ball'); elonTrashCan.throwAway('empty Starbucks cup of coffee'); elonTrashCan.throwAway('empty package of Cookies'); elonTrashCan.clean(); elonTrashCan.items = ['SpaceX secret project']; console.log(elonTrashCan.isEmpty()); // --> ???
کاملا تابلو که خروجی false هست. خب ما این رو نمیخوایم. Elon Musk هم حتی اینو نمیخواد. خب راه حل استفاده از Closure ها هست (به این پستم نگاهی بنداز). ما از Closure ها برای encapsulation استفاده میکنیم.
خب حالا فهمیدی که قضیه از چه قراره. پس برای اینکه بیایم تو جاوااسکریپت encapsulation رو رعایت کنیم به این صورت میتونیم عمل بکنیم:
123456789101112var items = []; var publicProperty = 'hi'; function throwAway(item) { items = [...items, item] }; function clean() { items = [] }; function isEmpty() { return items.length === 0 }; module.exports = { publicProperty, throwAway, isEmpty, clean };
خب اینم یه راه که براتون encapsulation رو شبیه سازی میکنه. اگه از یه transpiler مثل Babel استفاده میکنید میتونی از همین پیاده سازی کلاس ES6 استفاده بکنید. ولی اگه از transpiler استفاده نمیخوای بکنی بهتره از module pattern استفاده بکنی.
قانون polymorphism
نکته ای که توی polymorphism اینه که معمولا در کنار abstraction استفاده میشه.
تو polymorphism میایم یه کلاس تعریف میکنیم و توش یه مشته متد تعریف میکنیم، implement اون متد ها، تو کلاس های مختلف به شیوه های مختلف هست. پس میتونیم در نهایت این حرکت رو بزنیم که یه آرایه تعریف بکنیم و توش بیایم اون کلاس هایی که از کلاس بیس inherit شدن رو بریزیم. حالا به راحتی میشه با اون آرایه کار کرد.
123456789class A { show() { console.log('hi') } }; class B extends A {}; class C extends A {}; let b = new B(); let c = new C(); let arr = [b, c]; for (let i = 0; i < arr.length; i++) { arr[i].show(); };
قانون Abstraction
این قانون میگه که شما وقتی یه کلاسی رو abstract میکنی
- نباید بشه ازش new کرد.
- از متد های static اون کلاس abstract هم نباید استفاده بشه کرد.
- حتی وقتی یه کلاسی از کلاس abstract مون inherit میکنه نباید بازم بشه از متد های static استفاده کرد.
- تنها در صورتی بشه از متد های static اون استفاده کرد که کلاس فرزند بیاد متد های static رو implement بکنه.
- از متد های معمولی باید بشه تو کلاس فرزند استفاده کرد. حتی اگه کلاس فرزند اون رو implement نکرده باشه.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586'use strict'; class Abstract { // A static abstract method. static foo() { if (this === Abstract) { // Error Type 2. Abstract methods can not be called directly. throw new TypeError("Can not call static abstract method foo."); } else if (this.foo === Abstract.foo) { // Error Type 3. The child has not implemented this method. throw new TypeError("Please implement static abstract method foo."); } else { // Error Type 5. The child has implemented this method but also called `super.foo()`. throw new TypeError("Do not call static abstract method foo from child."); } } constructor() { if (this.constructor === Abstract) { // Error Type 1. Abstract class can not be constructed. throw new TypeError("Can not construct abstract class."); } //else (called from child) // Check if all instance methods are implemented. if (this.foo === Abstract.prototype.foo) { // Error Type 4. Child has not implemented this abstract method. throw new TypeError("Please implement abstract method foo."); } } // An abstract method. foo() { // Error Type 6. The child has implemented this method but also called `super.foo()`. throw new TypeError("Do not call abstract method foo from child."); } } // Error Type 1. //let bar = new Abstract(); // Throws because abstract class can not be constructed. // Error Type 2. //Abstract.foo(); // Throws because static abstract methods can not be called. class ChildA extends Abstract {} // Error Type 3. //ChildA.foo(); // Throws because ChildA does not implement static abstract method foo. // Error Type 4. //let bar = new ChildA(); // Throws because ChildA does not implement abstract method foo. class ChildB extends Abstract { static foo() { // Calls Abstract.foo(); super.foo(); } foo() { // Calls Abstract.prototype.foo(); super.foo(); } } // Error Type 5. //ChildB.foo(); // Throws because in ChildB the implementation calls the static abstract method foo. // Error Type 6. //(new ChildB()).foo(); // Throws because in ChildB the implementation calls the abstract method foo. class ChildC extends Abstract { static foo() { // Implementation of abstract static method. console.log('ChildC.foo'); } constructor() { super(); // Implementation of constructor. } foo() { // Implementation of abstract method. console.log('ChildC.prototype.foo'); } } // Success. ChildC.foo(); let bar = new ChildC(); bar.foo();
قانون Inheritance
تو این قانون یه کلاس بیس داریم که یه کلاس دیگه از اون inherit میکنه. مزیت وراثت توی اینه که مثلا یه کلاس ماشین تعریف میکنی و توش مسائل پایه ای رو هندل میکنی. بعد مثلا کلاس ون از اون inherit میشه. حالا تو کلاس ون تمام مسائل پایه ای رو داریم بعلاوه یسری متد ها و پراپرتی های خاص کلاس ون.