Введение в объектно-ориентированный JavaScript - Инкапсуляция, наследование и полиморфизм

ОГЛАВЛЕНИЕ

Инкапсуляция

Инкапсуляция является полезной частью объектно-ориентированного программирования, изолирующей данные в экземпляре класса от данных в другом экземпляре того же самого класса. Вот почему оператор this используется внутри класса для извлечения данных для данной переменной внутри данного экземпляра класса.

Открытые, защищенные и закрытые члены

Инкапсуляция реализуется в JavaScript путем отделения данных экземпляра внутри класса. Однако нет разных степеней инкапсуляции посредством операторов public, protected и private. Отсюда следует, что доступ к данным нельзя ограничить, как в других объектно-ориентированных языках программирования. Причина в том, что в JavaScript так делать просто не нужно для весьма крупных проектов. К свойствам и методам класса можно обращаться откуда угодно, внутри класса или вне его.

Инкапсуляция на практике

Пример инкапсуляции показан ниже:

..

function MyClass()
{
  this.MyData = "Some Text";
}

MyClass.prototype.MyFunction = function(newtext)
{
  this.MyData = newtext;
 
  alert("New text:\n"+this.MyData);
}

..

var c = new MyClass();
c.MyFunction("Some More Text");

var c2 = new MyClass();
c2.MyFunction("Some Different Text");

При вызове c.MyData возвращает "Some More Text", а c2.MyData возвращает "Some Different Text", показывая, что данные инкапсулированы внутри класса.

Вывод по инкапсуляции

Инкапсуляция – важная часть объектно-ориентированного программирования, чтобы данные в разных экземплярах класса были отделены друг от друга; это реализуется в JavaScript с помощью оператора this. Однако, в отличие от других объектно-ориентированных языков программирования, JavaScript не ограничивает доступ к данным внутри экземпляра класса.

Наследование

Наследование свойств

Как сказано выше в статье, в JavaScript нет прямого наследования, так как он является языком прототипов. Поэтому для наследования класса от другого класса используется оператор prototype, чтобы клонировать конструктор родительского класса и при этом унаследовать его методы и свойства. Конструктор родительского класса также вызывается в конструкторе подкласса, чтобы применить все его методы и свойства к подклассу, как показано в коде ниже:

..

//Конструктор родительского класса
function Animal(name)
{
  this.name = name;
}

//Конструктор унаследованного класса
function Dog(name)
{
  Animal.call(this, name);
}
Dog.prototype = new Animal();

Dog.prototype.ChangeName(newname)
{
  this.name = newname;
}

..

В примере кода выше создаются два класса — базовый класс по имени Animal(животное) и подкласс по имени Dog(собака), унаследованный от Animal. В конструкторе базового класса создается свойство по имени name, которому присваивается переданное ему значение.

При создании унаследованного класса нужны две строки кода для наследования от базового класса, как показано для Dog:

Animal.call(this, name);

Эта строка кода вызывается из конструктора подкласса. call() - функция JavaScript, вызывающая функцию в заданном контексте (первый аргумент). Аргументы, необходимые вызываемой функции, также передаются, начиная со второго аргумента call(), как видно для name. Это означает, что конструктор базового класса вызывается из конструктора подкласса, тем самым применяя к подклассу методы и свойства, созданные в Animal.

Вторая строка кода, необходимая для наследования от базового класса, следующая:

Dog.prototype = new Animal();

Эта строка кода делает прототип для унаследованного класса (клонирующий родительский конструктор при его использовании) новым экземпляром родительского класса, тем самым наследуя любые методы или свойства в методах в подклассе.

Следует отметить, что как только подкласс был унаследован от родительского класса, к любым данным, к которым надо обратиться из родительского класса, можно обратиться с помощью оператора this из подкласса, так как методы и свойства теперь стали частью объекта подкласса.

Наследование методов

Как свойства, методы могут наследоваться от родительского класса в JavaScript, аналогично наследованию свойств, как показано ниже:

..

//Конструктор родительского класса
function Animal(name)
{
  this.name = name;
}

//Метод родительского класса
Animal.prototype.alertName = function()
{
  alert(this.name);
}

//Конструктор унаследованного класса
function Dog(name)
{
  Animal.call(this, name);
 
  this.collarText;
}
Dog.prototype = new Animal();

Dog.prototype.setCollarText = function(text)
{
  this.collarText = text;
}

..

Унаследованный метод вызывается так:

var d = new Dog("Fido");
d.alertName();

Это вызывает alertName(), являющийся унаследованным методом Animal.

Создание экземпляра унаследованного класса

Классы, унаследованные от другого класса, могут вызываться в JavaScript, как вызывался бы базовый класс, и методы и свойства могут вызываться аналогично.

var d = new Dog("Fido");      //Создает экземпляр подкласса
alert(d.name);                //Извлекает данные, унаследованные от родительского //класса
d.setCollarText("FIDO");      //Вызывает метод подкласса

Методы, унаследованные в подклассе, могут быть вызваны аналогично с помощью имени переменной вместо оператора this:

var d = new Dog("Fido");      // Создает экземпляр подкласса
d.alertName();                //Вызывает унаследованный метод

Вывод по наследованию

Наследование – один из трех важных принципов объектно-ориентированного программирования. Он реализуется в JavaScript с помощью prototype и функции call().

Полиморфизм

Полиморфизм – расширение принципа наследования в объектно-ориентированном программировании, реализуемое в JavaScript с помощью оператора prototype. Полиморфизм – это когда подкласс класса может вызвать ту же самую обобщенную унаследованную функцию в своем собственном контексте. Например:

..

//Конструктор родительского класса
function Animal(name)
{
  this.name = name;
}

Animal.prototype.speak = function()
{
  alert(this.name + " says:");
}

//Конструктор унаследованного класса "Dog"
function Dog(name)
{
  Animal.call(this, name);
}

Dog.prototype.speak = function()
{
  Animal.prototype.speak.call(this);
 
  alert("woof");
}

//Конструктор унаследованного класса "Cat"
function Cat(name)
{
  Animal.call(this, name);
}

Cat.prototype.speak = function()
{
  Animal.prototype.speak.call(this);
 
  alert("miaow");
}

..

Этот код означает, что если вызывается экземпляр Dog, а затем вызывается функция speak() класса Dog, то она переопределит функцию speak() родительского класса. Однако, хотя мы хотим делать нечто разное с версией speak() каждого подкласса, мы хотим вызывать обобщенную функцию speak() родительского класса с помощью Animal.prototype.speak.call(this);, который вызывает унаследованную функцию в контексте подкласса. Затем, после того как мы делаем что-то еще с ней, что для Dog является alert("woof"); а для Cat является alert("miaow");

При вызове это выглядело бы так:

var d = new Dog("Fido");     //Создает экземпляр Dog
d.speak();                   //Вызывает функцию speak() класса Dog
 
var c = new Cat("Lucy");     //Создает экземпляр Cat
c.speak();                   //Вызывает функцию speak() класса Cat

Первые две строки уведомляют "Fido говорит:" (функция speak() родительского класса), далее следует "woof" (функция speak() класса Dog).

Следующие две строки уведомляют "Lucy говорит:" (функция speak() родительского класса), далее следует "miaow" (функция speak() класса Cat).

Вывод по полиморфизму

Полиморфизм – очень полезная часть объектно-ориентированного программирования, и хотя в данной статье он не разобран очень глубоко, принципы остаются неизменными и могут применяться в таком виде в большинстве аспектов полиморфизма в JavaScript.

Вывод

Прочитав данную статью, вы должны уметь:
•    Создавать классы с методами и свойствами
•    Создавать унаследованные классы
•    Создавать полиморфные функции

Это лишь вводная статья, но надеемся, что вы сможете использовать приобретенные навыки для более сложных объектно-ориентированных структур.