If you're seeing this message, it means we're having trouble loading external resources on our website.

Ако си зад уеб филтър, моля, увери се, че домейните *. kastatic.org и *. kasandbox.org са разрешени.

Основно съдържание

Преговор: Обектно-ориентиран дизайн

Това е преговор на нещата, които научихме в урока за обектно-ориентиран дизайн.
Когато създаваме програми, често разбираме, че искаме да създадем много различни обекти, които споделят общи свойства – като много котки, които се различават по цвета на козината и размера си, или много бутони с различни надписи и позиции. Искаме да можем да кажем "това обобщава какво е котката" а след това да кажем "да създадем тази конкретна котка и тази друга котка, а те ще си приличат по някакъв начин и ще се различават до други начини". В този случай искаме да използваме обектно-ориентиран дизайн, за да дефинираме типове и да създаваме нови инстанции на тези обекти.
За да дефинираме тип в JavaScript, трябва първо да дефинираме "функция конструктор". Това е функцията, която използваме винаги, когато искаме да създадем нова инстанция от този тип. Ето функцията конструктор за типа Book:
var Book = function(title, author, numPages) {
  this.title = title;
  this.author = author;
  this.numPages = numPages;
  this.currentPage = 0;
};
Функцията приема аргументи за аспектите, които ще са различни за всяка книга - заглавието, автора и броя страници. След това задава началните свойства на обекта според тези аргументи, като използва ключовата дума this. Когато използваме this в обект, се обръщаме към конкретната инстанция на обекта, обръщаме се към самия него. Трябва да запазим свойствата в this, за да сме сигурни, че можем да си ги спомним по-късно.
За да създадем инстанция на обекта Book, декларираме нова променлива, в която да го запазим, след това използваме ключовата дума new, последвана от името на функцията конструктор, и подаваме аргументите, които конструкторът очаква:
var book = new Book("Роботски сънища", "Айзък Азимов", 320);
След това можем да достъпим всяко свойство, което сме запазили в обекта с точкова нотация:
println("С удоволствие прочетох " + book.title); // С удоволствие прочетох Роботски сънища
println(book.author + " е любимият ми автор"); // Айзък Азимов е любимият ми автор
Нека да покажем какво щеше да се случи, ако не бяхме задали функцията конструктор правилно:
var Book = function(title, author, numPages) {
};
var book = new Book("Малкият брат", "Кори Доктороу", 380);
println("С удоволствие прочетох " + book.title); // С удоволствие прочетох undefined
println(book.author + " is my fav author"); // undefined е любимият ми автор
Ако подадем аргументите на функцията конструктор, но не ги запазим в this, тогава няма да можем да ги достъпим по-късно! Обектът отдавна ще е забравил за тях.
Когато дефинираме типове, често искаме те да имат както свойства, така и поведение – например всички наши котки трябва да могат да мяукат с метода meow() и да се хранят с метода eat(). Затова трябва да можем да добавяме функции към дефинициите на нашите типове обекти, а това можем да направим като ги дефинираме в т.нар. прототип на обекта (object prototype):
Book.prototype.readItAll = function() {
  this.currentPage = this.numPages;
  println("Ти прочете " + this.numPages + " страници!");
};
Това става по същия начин, по който обикновено дефинираме функция, само че в този случай я прикачаме към прототипа на обекта Book, вместо просто да я дефинираме глобално. Така JavaScript знае, че това е функция, която може да бъде извикана върху всеки обект Book, и че тази функция трябва да има достъп до контекста this на обекта, върху който е извикана.
След това можем да извикваме функцията (която наричаме метод, тъй като е прикрепена към обект) ето така:
var book = new Book("Фермата на животните", "Джордж Оруел", 112);
book.readItAll(); // Ти прочете 112 страници!
Запомни, че целият смисъл на обектно-ориентираният дизайн е, че за нас става лесно да създаваме множество свързани обекти (инстанции на обекти). Да видим това в кода:
var pirate = new Book("Пиратско кино", "Кори Доктороу", 384);
var giver = new Book("Пазителят", "Лоис Лаури", 179);
var tuck = new Book("Безсмъртните Тък", "Натали Бабит", 144);

pirate.readItAll(); // Ти прочете 384 страници!
giver.readItAll(); // Ти прочете 179 страници!
tuck.readItAll(); // Ти прочете 144 страници!
Този код ни дава три подобни книги – имат еднакви свойства и поведение, но в същото време са различни. Супер!
Ако помислиш за реалния свят, котките и кучетата са различни типове обекти, затова вероятно трябва да създадеш различни типове за тях, ако програмираш обектите Cat и Dog. Котката би мяукала с метода meow(), а кучето би джафкало с метода bark(). Но все пак си приличат – и котката, и кучето се хранят с метода eat(), имат възраст с метода age, както и имат свойствата раждане birth и смърт death. И двата типа са бозайници, а това значи, че имат много общи неща, макар и да се различават.
В този случай искаме да използваме идеята за наследяване на обекти. Един тип може да наследи свойствата и поведението на родителски тип, но може да има и собствени уникални неща. Всички обекти Cat и Dog могат да наследят Mammal (Бозайник), за да не се налага да създават метода за хранене eat() от нулата. Как бихме направили това с JavaScript?
Да се върнем на нашия пример и да приемем, че Book е "родителски" тип, а ние искаме да направим два типа, които го наследяват – хартиена книга Paperback и електронна книга EBook.
Хартиената книга е книга, но има една основна разлика, поне в нашата програма: има изображение на корицата. Затова нашият конструктор трябва да приеме четири аргумента, за да добави допълнителната информация:
var PaperBack = function(title, author, numPages, cover) {
  // ...
}
Не искаме отново да вършим цялата работа, която вече сме свършили в конструктора на Book, за да запазим тези първи 3 аргумента – искаме да използваме факта, че кодът за тях ще е един и същ. Тогава можем да извикаме конструктора на Book от конструктора на PaperBack и да му подадем тези аргументи:
var PaperBack = function(title, author, numPages, cover) {
  Book.call(this, title, author, numPages);
  // ...
};
Все пак трябва да запазим свойството за корицата cover в обекта, затова ни трябва още един ред, за да се погрижим за това:
var PaperBack = function(title, author, numPages, cover) {
  Book.call(this, title, author, numPages);
  this.cover = cover;
};
Вече имаме конструктор за нашия тип PaperBack, който ни помага да споделяме свойствата на Book, но искаме и PaperBack да наследи тези методи. Ето как да направим това, като кажем на програмата, че прототипът на PaperBack трябва да се основава на прототипа на Book:
PaperBack.prototype = Object.create(Book.prototype);
Освен това може да искаме да добавим специфично поведение за хартиената книга като способността да гори, а това можем да направим като дефинираме функции на прототипа след горния ред:
PaperBack.prototype.burn = function() {
  println("Леле, ти изгори всичките " + this.numPages + " страници");
  this.numPages = 0;
};
И вече можем да създадем хартиена книга, да я прочетем и да я изгорим!
var calvin = new PaperBack("Калвин и Хобс", "Бил Уотерсън", 256, "http://ecx.images-amazon.com/images/I/61M41hxr0zL.jpg");

calvin.readItAll(); // Ти прочете 256 страници!
calvin.burn(); // Леле, ти изгори всичките 256 страници!
(Е, няма наистина да я изгорим, защото тази книга е чудесна, но вероятно, ако сме загубени в ледена пустиня и сме отчаяни за топлина и сме на косъм от смъртта – бихме го направили.)
И така можеш да видиш как използваме принципите на обектно-ориентираното програмиране в JavaScript, за да създадем по-сложни данни за програмите и да моделираме по-добре света на тези програми.

Искаш ли да се присъединиш към разговора?

Все още няма публикации.
Разбираш ли английски? Натисни тук, за да видиш още дискусии в английския сайт на Кан Академия.