Основно съдържание
Програмиране
Курс: Програмиране > Раздел 1
Урок 16: Обектно-ориентиран дизайнПреговор: Обектно-ориентиран дизайн
Това е преговор на нещата, които научихме в урока за обектно-ориентиран дизайн.
Когато създаваме програми, често разбираме, че искаме да създадем много различни обекти, които споделят общи свойства – като много котки, които се различават по цвета на козината и размера си, или много бутони с различни надписи и позиции. Искаме да можем да кажем "това обобщава какво е котката" а след това да кажем "да създадем тази конкретна котка и тази друга котка, а те ще си приличат по някакъв начин и ще се различават до други начини". В този случай искаме да използваме обектно-ориентиран дизайн, за да дефинираме типове и да създаваме нови инстанции на тези обекти.
За да дефинираме тип в 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, за да създадем по-сложни данни за програмите и да моделираме по-добре света на тези програми.
Искаш ли да се присъединиш към разговора?
Все още няма публикации.