Uvod v objektno usmerjeno programiranje v JavaScript

Ta članek je namenjen študentom JavaScript, ki nimajo predznanja iz objektno usmerjenega programiranja (OOP). Osredotočam se na dele OOP, ki so pomembni samo za JavaScript in ne OOP na splošno. Polimorfizem preskočim, ker se bolje prilega jeziku s statičnim tipom.

Zakaj morate to vedeti?

Ste izbrali JavaScript za svoj prvi programski jezik? Ali želite biti vroči razvijalec, ki dela na velikih podjetniških sistemih, ki obsegajo sto tisoč vrstic kode ali več?

Če se ne naučite popolnoma sprejeti objektno usmerjenega programiranja, boste dobro in resnično izgubljeni.

Različni načini razmišljanja

V nogometu lahko igrate iz varne obrambe, lahko igrate z visokimi žogami s strani ali pa napadate, kot da jutri ni. Vse te strategije imajo isti cilj: zmagati v igri.

Enako velja za paradigme programiranja. Obstajajo različni načini za reševanje problema in oblikovanje rešitve.

Objektno usmerjeno programiranje ali OOP je paradigma sodobnega razvoja aplikacij. Podpirajo ga glavni jeziki, kot so Java, C # ali JavaScript.

Objektno usmerjena paradigma

Z vidika OOP je aplikacija zbirka "predmetov", ki komunicirajo med seboj. Te predmete temeljimo na stvareh v resničnem svetu, kot so izdelki na zalogi ali evidence zaposlenih. Predmeti vsebujejo podatke in izvajajo določeno logiko na podlagi svojih podatkov. Kot rezultat tega je kodo OOP zelo enostavno razumeti. Kar ni tako enostavno, je odločitev, kako najprej razbiti aplikacijo v te majhne predmete.

Če ste bili takšni kot jaz, ko sem prvič slišal, sploh ne veste, kaj to dejansko pomeni - vse skupaj zveni zelo abstraktno. Tako počutje je popolnoma v redu. Pomembneje je, da ste idejo že slišali, si jo zapomnili in poskusili uporabiti OOP v svoji kodi. Sčasoma boste pridobili izkušnje in več svoje kode uskladili s tem teoretičnim konceptom.

Lekcija : OOP, ki temelji na resničnih predmetih, omogoča vsakomur, da prebere vašo kodo in razume, kaj se dogaja.

Predmet kot osrednji del

Preprost primer vam bo pomagal videti, kako JavaScript izvaja temeljna načela OOP. Razmislite o primeru nakupa, v katerem izdelke shranite v košarico, nato pa izračunajte skupno ceno, ki jo morate plačati. Če vzamete svoje znanje JavaScript in kodirate primer uporabe brez OOP, bi to izgledalo takole:

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 perspektiva olajša pisanje boljše kode, ker predmete razmišljamo, kot bi jih srečali v resničnem svetu. Ker naš primer uporabe vsebuje košarico izdelkov, imamo že dve vrsti predmetov - predmet košarice in predmete izdelka.

Različico OOP primera nakupovalne uporabe bi lahko zapisali tako:

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();

Kot lahko vidite v prvi vrstici, ustvarimo nov objekt z uporabo ključne besede, ki newji sledi ime razreda (opisano spodaj). To vrne predmet, ki ga shranimo v spremenljivko kruh. To ponovimo za spremenljivo vodo in po podobni poti ustvarimo spremenljivo košaro. Ko dodate te izdelke v košarico, končno natisnete skupni znesek, ki ga morate plačati.

Razlika med dvema delčkoma kode je očitna. Različica OOP se skoraj bere kot pravi angleški stavki in zlahka ugotovite, kaj se dogaja.

Lekcija : Predmet po vzoru stvari iz resničnega sveta je sestavljen iz podatkov in funkcij.

Razred kot predloga

Razrede v OOP uporabljamo kot predloge za ustvarjanje predmetov. Objekt je "primerek razreda", "instancacija" pa je ustvarjanje predmeta na podlagi razreda. Koda je definirana v razredu, vendar je ni mogoče izvesti, razen če je v predmetu v živo.

Predavanja si lahko ogledate kot načrti avtomobila. Določajo lastnosti avtomobila, kot sta navor in konjska moč, notranje funkcije, kot so razmerja zrak-gorivo, in javno dostopne metode, kot je vžig. Šele ko tovarna ustvari avtomobil, lahko ključ obrnete in se odpeljete.

V našem primeru uporabe uporabljamo razred Product za primerjanje dveh predmetov, kruha in vode. Seveda ti predmeti potrebujejo kodo, ki jo morate navesti v razredih. Gre takole:

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()); };}

Predavanje v JavaScript je videti kot funkcija, vendar ga uporabljate drugače. Ime funkcije je ime razreda in je napisano z veliko začetnico. Ker ne vrne ničesar, funkcije ne pokličemo na običajen način, kot je const basket = Product('bread', 1);. Namesto tega dodamo ključno besedo všeč const basket = new Product('bread', 1);.

Koda znotraj funkcije je konstruktor. Ta koda se izvede vsakič, ko se objekt ustvari. Izdelek ima parametre _namein _price. Vsak nov objekt shrani te vrednosti v sebi.

Poleg tega lahko določimo funkcije, ki jih bo objekt zagotavljal. Te funkcije definiramo tako, da predpišemo to ključno besedo, zaradi česar so dostopni od zunaj (glejte Kapsulacija). Upoštevajte, da imajo funkcije popoln dostop do lastnosti.

Za ustvarjanje novega predmeta Class Basket ne zahteva nobenih argumentov. Namestitev novega predmeta Basket preprosto ustvari prazen seznam izdelkov, ki jih lahko program nato napolni.

Lekcija : Razred je predloga za ustvarjanje predmetov med izvajanjem.

Kapsulacija

Morda boste naleteli na drugo različico razglasitve razreda:

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

Upoštevajte dodelitev lastnosti spremenljivki this. Na prvi pogled se zdi boljša različica, ker ne potrebuje več metod getter (getName & getPrice) in je zato krajša.

Na žalost ste zdaj dali popoln dostop do lastnosti od zunaj. Tako bi lahko do njega dostopali in ga spreminjali vsi:

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