مقدمة إلى البرمجة الشيئية في JavaScript

هذه المقالة مخصصة لطلاب JavaScript الذين ليس لديهم أي معرفة سابقة في البرمجة الموجهة للكائنات (OOP). أركز على أجزاء OOP ذات الصلة فقط بـ JavaScript وليس OOP بشكل عام. لقد تخطيت تعدد الأشكال لأنه يتناسب بشكل أفضل مع لغة مكتوبة ثابتة.

لماذا عليك أن تعرف هذا؟

هل اخترت JavaScript لتكون لغتك البرمجية الأولى؟ هل تريد أن تكون مطورًا متميزًا يعمل على أنظمة المؤسسات العملاقة التي تغطي مئات الآلاف من الأسطر من التعليمات البرمجية أو أكثر؟

ما لم تتعلم تبني البرمجة الشيئية بالكامل ، فسوف تضيع حقًا.

عقليات مختلفة

في كرة القدم ، يمكنك اللعب من دفاع آمن ، ويمكنك اللعب بكرات عالية من الجانبين أو يمكنك الهجوم كما لو لم يكن هناك غدًا. كل هذه الاستراتيجيات لها نفس الهدف: الفوز باللعبة.

وينطبق الشيء نفسه على نماذج البرمجة. هناك طرق مختلفة للتعامل مع مشكلة وتصميم حل.

البرمجة الموجهة للكائنات ، أو OOP ، هي نموذج لتطوير التطبيقات الحديثة. وهو مدعوم من قبل اللغات الرئيسية مثل Java أو C # أو JavaScript.

النموذج الكينوني

من منظور OOP ، التطبيق عبارة عن مجموعة من "الكائنات" التي تتواصل مع بعضها البعض. نحن نبني هذه العناصر على أشياء في العالم الحقيقي ، مثل المنتجات الموجودة في المخزون أو سجلات الموظفين. تحتوي الكائنات على بيانات وتقوم ببعض المنطق بناءً على بياناتها. نتيجة لذلك ، من السهل جدًا فهم رمز OOP. ما ليس سهلاً هو تحديد كيفية تقسيم تطبيق إلى هذه الكائنات الصغيرة في المقام الأول.

إذا كنت مثلي عندما سمعته في المرة الأولى ، فليس لديك أدنى فكرة عما يعنيه هذا في الواقع - كل هذا يبدو مجرّدًا للغاية. الشعور بهذه الطريقة جيد تمامًا. الأهم من ذلك أنك سمعت الفكرة وتذكرها وحاول تطبيق OOP في التعليمات البرمجية الخاصة بك. بمرور الوقت ، ستكتسب الخبرة وتوائم المزيد من التعليمات البرمجية الخاصة بك مع هذا المفهوم النظري.

الدرس : OOP استنادًا إلى كائنات العالم الحقيقي يتيح لأي شخص قراءة التعليمات البرمجية الخاصة بك وفهم ما يحدث.

الكائن كمحور

سيساعدك مثال بسيط في معرفة كيفية تنفيذ JavaScript للمبادئ الأساسية لـ OOP. ضع في اعتبارك حالة استخدام التسوق التي تضع فيها المنتجات في سلتك ثم احسب السعر الإجمالي الذي يجب أن تدفعه. إذا أخذت معرفتك بـ JavaScript ورمزت حالة الاستخدام بدون OOP ، فستبدو كما يلي:

const bread = {name: 'Bread', price: 1};const water = {name: 'Water', price: 0.25};
const basket = [];basket.push(bread);basket.push(bread);basket.push(water);basket.push(water);basket.push(water);
const total = basket .map(product => product.price) .reduce((a, b) => a + b, 0);
console.log('one has to pay in total: ' + total);

يجعل منظور OOP كتابة كود أفضل أسهل لأننا نفكر في الأشياء كما نواجهها في العالم الحقيقي. نظرًا لأن حالة الاستخدام الخاصة بنا تحتوي على سلة من المنتجات ، فلدينا بالفعل نوعان من العناصر - كائن السلة وكائنات المنتج.

يمكن كتابة نسخة OOP من حالة استخدام التسوق مثل:

const bread = new Product('bread', 1);const water = new Product('water', .25)const basket = new Basket();basket.addProduct(2, bread);basket.addProduct(3, water);basket.printShoppingInfo();

كما ترى في السطر الأول ، نقوم بإنشاء كائن جديد باستخدام الكلمة الأساسية newمتبوعة باسم ما يسمى بالفئة (الموضحة أدناه). هذا يعيد الكائن الذي نقوم بتخزينه في الخبز المتغير. نكرر ذلك بالنسبة للمياه المتغيرة ونسلك مسارًا مشابهًا لإنشاء سلة متغيرة. بعد إضافة هذه المنتجات إلى سلتك ، تقوم أخيرًا بطباعة المبلغ الإجمالي الذي يتعين عليك دفعه.

الفرق بين مقتطفات التعليمات البرمجية واضح. تقرأ نسخة OOP تقريبًا مثل الجمل الإنجليزية الحقيقية ويمكنك بسهولة معرفة ما يحدث.

الدرس : الكائن المصمم على غرار الأشياء الواقعية يتكون من بيانات ووظائف.

فئة كقالب

نستخدم الفئات في OOP كقوالب لإنشاء الكائنات. الكائن هو "مثيل لفئة" و "إنشاء مثيل" هو إنشاء كائن بناءً على فئة. تم تعريف الكود في الفئة ولكن لا يمكن تنفيذه ما لم يكن في كائن حي.

يمكنك إلقاء نظرة على فصول مثل مخططات سيارة. يحددون خصائص السيارة مثل عزم الدوران والقدرة الحصانية ، والوظائف الداخلية مثل نسب الهواء إلى الوقود والطرق المتاحة للجمهور مثل الإشعال. فقط عندما يقوم المصنع بإنشاء السيارة ، يمكنك تشغيل المفتاح والقيادة.

في حالة الاستخدام الخاصة بنا ، نستخدم فئة المنتج لإنشاء مثيل لكائنين ، الخبز والماء. بالطبع ، تحتاج هذه الكائنات إلى رمز يجب عليك توفيره في الفصول الدراسية. يذهب مثل هذا:

function Product(_name, _price) { const name = _name; const price = _price;
this.getName = function() { return name; };
this.getPrice = function() { return price; };}
function Basket() { const products = [];
this.addProduct = function(amount, product) { products.push(...Array(amount).fill(product)); };
this.calcTotal = function() { return products .map(product => product.getPrice()) .reduce((a, b) => a + b, 0); };
this.printShoppingInfo = function() { console.log('one has to pay in total: ' + this.calcTotal()); };}

تبدو الفئة في JavaScript كدالة ، لكنك تستخدمها بشكل مختلف. اسم الوظيفة هو اسم الفئة ويتم كتابته بأحرف كبيرة. نظرًا لأنه لا يُرجع أي شيء ، فإننا لا نستدعي الوظيفة بالطريقة المعتادة مثل const basket = Product('bread', 1);. بدلاً من ذلك ، نضيف الكلمة الرئيسية الجديدة مثل const basket = new Product('bread', 1);.

الكود الموجود داخل الوظيفة هو المُنشئ. يتم تنفيذ هذا الرمز في كل مرة يتم فيها إنشاء كائن. المنتج يحتوي على المعلمات _nameو _price. كل كائن جديد يخزن هذه القيم بداخله.

علاوة على ذلك ، يمكننا تحديد الوظائف التي سيوفرها الكائن. نحدد هذه الوظيفة عن طريق إضافة هذه الكلمة الأساسية مسبقًا مما يجعلها قابلة للوصول من الخارج (انظر التغليف). لاحظ أن الوظائف لها حق الوصول الكامل إلى الخصائص.

لا تتطلب سلة الفئات أي وسيطات لإنشاء كائن جديد. يؤدي إنشاء كائن سلة جديد إلى إنشاء قائمة فارغة من المنتجات التي يمكن للبرنامج ملؤها بعد ذلك.

الدرس : الفصل هو قالب لتوليد الكائنات أثناء وقت التشغيل.

التغليف

قد تصادف إصدارًا آخر لكيفية إعلان الفصل:

function Product(name, price) { this.name = name; this.price = price;}

مانع تخصيص الخصائص للمتغير this. للوهلة الأولى ، يبدو أنه إصدار أفضل لأنه لا يتطلب أساليب getter (getName & getPrice) بعد الآن وبالتالي فهو أقصر.

لسوء الحظ ، لقد منحت الآن حق الوصول الكامل إلى العقارات من الخارج. لذلك يمكن للجميع الوصول إليه وتعديله:

const bread = new Product('bread', 1);bread.price = -10;

This is something you don’t want as it makes the application more difficult to maintain. What would happen if you added validation code to prevent, for example, prices less than zero? Any code that accesses the price property directly would bypass the validation. This could introduce errors that would be difficult to trace. Code that uses the object’s getter methods, on the other hand, is guaranteed to go through the object’s price validation.

Objects should have exclusive control over their data. In other words, the objects “encapsulate” their data and prevent other objects from accessing the data directly. The only way to access the data is indirect via the functions written into the objects.

Data and processing (aka logic) belong together. This is especially true when it comes to larger applications where it is very important that processing data is restricted to specifically-defined places.

Done right, OOP produces modularity by design, the holy grail in software development. It keeps away the feared spaghetti-code where everything is tightly coupled and you don’t know what happens when you change a small piece of code.

In our case, objects of class Product don’t let you change the price or the name after their initialization. The instances of Product are read-only.

Lesson: Encapsulation prevents access to data except through the object’s functions.

Inheritance

Inheritance lets you create a new class by extending an existing class with additional properties and functions. The new class “inherits” all of the features of its parent, avoiding the creation of new code from scratch. Furthermore, any changes made to the parent class will automatically be available to the child class. This makes updates much easier.

Let’s say we have a new class called Book that has a name, a price and an author. With inheritance, you can say that a Book is the same as a Product but with the additional author property. We say that Product is the superclass of Book and Book is a subclass of Product:

function Book(_name, _price, _author) { Product.call(this, _name, _price); const author = _author; this.getAuthor = function() { return author; }}

Note the additional Product.call along the this as the first argument. Please be aware: Although book provides the getter methods, it still doesn’t have direct access to the properties name and price. Book must call that data from the Product class.

You can now add a book object to the basket without any issues:

const faust = new Book('faust', 12.5, 'Goethe');basket.addProduct(1, faust);

Basket expects an object of type Product. Since book inherits from Product through Book, it is also a Product.

Lesson: Subclasses can inherit properties and functions from superclasses while adding properties and functions of their own.

JavaScript and OOP

You will find three different programming paradigms used to create JavaScript applications. They are Prototype-Based Programming, Object-Oriented Programming and Functional-Oriented Programming.

The reason for this lies in JavaScript’s history. Originally, it was prototype-based. JavaScript was not intended as a language for large applications.

Against the plan of its founders, developers increasingly used JavaScript for bigger applications. OOP was grafted on top of the original prototype-based technique.

The prototype-based approach is shown below. It is seen as the “classical and default way” to construct classes. Unfortunately it does not support encapsulation.

Even though JavaScript’s support for OOP is not at the same level as other languages like Java, it is still evolving. The release of version ES6 added a dedicated class keyword we could use. Internally, it serves the same purpose as the prototype property, but it reduces the size of the code. However, ES6 classes still lack private properties, which is why I stuck to the “old way”.

For the sake of completeness, this is how we would write the Product, Basket and Book with ES6 classes and also with the prototype (classical and default) approach. Please note that these versions don’t provide encapsulation:

// ES6 version
class Product { constructor(name, price) { this.name = name; this.price = price; }}
class Book extends Product { constructor(name, price, author) { super(name, price); this.author = author; }}
class Basket { constructor() { this.products = []; }
 addProduct(amount, product) { this.products.push(…Array(amount).fill(product)); }
 calcTotal() { return this.products .map(product => product.price) .reduce((a, b) => a + b, 0); }
 printShoppingInfo() { console.log('one has to pay in total: ' + this.calcTotal()); }}
const bread = new Product('bread', 1);const water = new Product('water', 0.25);const faust = new Book('faust', 12.5, 'Goethe');
const basket = new Basket();basket.addProduct(2, bread);basket.addProduct(3, water);basket.addProduct(1, faust);basket.printShoppingInfo();
//Prototype versionfunction Product(name, price) { this.name = name; this.price = price;}function Book(name, price, author) { Product.call(this, name, price); this.author = author;}Book.prototype = Object.create(Product.prototype);Book.prototype.constructor = Book;function Basket() { this.products = [];}Basket.prototype.addProduct = function(amount, product) { this.products.push(...Array(amount).fill(product));};Basket.prototype.calcTotal = function() { return this.products .map(product => product.price) .reduce((a, b) => a + b, 0);};Basket.prototype.printShoppingInfo = function() { console.log('one has to pay in total: ' + this.calcTotal());};

Lesson: OOP was added to JavaScript later in its development.

Summary

As a new programmer learning JavaScript, it will take time to appreciate Object-Oriented Programming fully. The important things to understand at this early stage are the principles the OOP paradigm is based on and the benefits they provide:

  • Objects modeled on real-world things are the centerpiece of any OOP-based application.
  • Encapsulation protects data from uncontrolled access.
  • Objects have functions that operate on the data the objects contain.
  • Classes are the templates used to instantiate objects.
  • Inheritance is a powerful tool for avoiding redundancy.
  • OOP is more verbose but easier to read than other coding paradigms.
  • Since OOP came later in JavaScript’s development, you may come across older code that uses prototype or functional programming techniques.

Further reading

  • //developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS
  • //voidcanvas.com/es6-private-variables/
  • //medium.com/@rajaraodv/is-class-in-es6-the-new-bad-part-6c4e6fe1ee65
  • //developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance

    * //en.wikipedia.org/wiki/Object-oriented_programming

  • //en.wikipedia.org/wiki/Object-oriented_programming