Объектно-ориентированное программирование в JavaScript, NoPrototype стиль [Object-oriented programming in JavaScript, NoPrototype style]
Большое количество статей присутствует в бескрайнем интернете об объектно-ориентированном программирование на JavaScript. Но мне как C# программисту, привыкшему к его ООП синтаксису и количеству возможностей, которые он дает, не одна из них не подходит. Почему же?
Задача
Что действительно нужно от ООП в JavaScript:
- Инкапсуляция
- Наследование
- Полиморфизм
NoPrototype
Многие способы реализации объект-ориентированного программирования в JavaScript основаны на использовании прототипа (например), но ни один из них не решает поставленную мной задачу. Данный способ не будет основан на прототипах, так что с этого следует и название стиля. Но об это далее.
Класс
Единственное, что нам нужно, так это выработать единый стиль написания класса в JavaScript.
Приведем пример класса на C#:
public class Book { // конструктор public Book(string newName) { name = newName; } // приватное поле private int id; // приватное поле private string name; // публичное поле public string Description; // публичное свойство public string Name { // getter get { return name; } // setter set { name = value; } } // приватный метод private void incrementId() { id++; } // публичный метод public void Buy() { Description += " (куплено)"; } }А теперь тот же класс только на JavaScript:
var Book = function (newName) { var self = this; // приватное поле var id = null; // приватное поле var name = null; // публичное поле self.Description = null; // приватный метод var incrementId = function () { id++; }; // публичное свойство self.Name = function (value) { if (value === undefined) { // getter return name; } else { // setter name = value; } }; // публичный метод self.Buy = function () { self.Description += " (куплено)"; }; // конструктор var constructor = function () { id = 0; name = newName; }; constructor(); };Очень важно соблюдать простые правила:
this
→self.
Использовать self вместо this. Потому что: this можно подменить, и если скрипт будет сжиматься, то ключевые слова не сжимаются.- Соблюдать порядок написания элементов:
- Приватные поля
- Публичные поля
- Приватные методы и приватные свойства. Порядок среди все приватных методов/свойств зависит от использованных внутри других приватных методов/свойств. Например, если метод A() использует метод B() и C(), то порядок написания методов будет: B, C, A.
- Публичные методы и публичные свойства. Порядок среди все публичных методов/свойств любой.
- Конструктор
Инкапсуляция
В рассмотренном выше примере мы использовали всего 2 модификатора доступа:
- private. Описание элемента начинается с "
var
". Доступ к элементу осуществляется по имени элемента. Например: если мы опишем приватное полеvar name = 'Some book';
то чтобы присвоить новое значение пишемname = 'New book';
- public. Описание элемента начинается с "
self.
". Доступ к элементу осуществляется по "self.
" + имя элемента. Например: описание публичного поляself.Description = null;
и его использованиеself.Description = 'New description';
C# | JavaScript |
public | public |
private | private |
protected | public |
internal | public |
Наследование
Для реализации наследования и полиморфизма воспользуемся следующим кодом:
function inherit(derivedInstance, baseClass, args) { var args = (args === undefined) ? [] : args; baseClass.apply(derivedInstance, args); var base = {}; for (var methodName in derivedInstance) (function() { var method = derivedInstance[methodName]; if (typeof (method) == "function") { base[methodName] = function() { return method.apply(derivedInstance, arguments); }; } })(); return base; }
Наследование реализовывается глобальной функцией
inherit
.Аргументы функции
inherit
:derivedInstance
- ссылка на объект, который будет наследоваться от классаbaseClass
baseClass
- ссылка на функцию, которая описывает базовый класс, именно от этого базового класса будет осуществляться наследованиеargs
- массив аргументов конструктора базового класса, то есть аргументы, необходимые для создания объекта классаbaseClass
- возвращаемое значение - это ссылка на объект, содержащий список базовых методов. Это необходимо для полиморфизма, об этом далее.
var Car = function () { var self = this; self.Drive = function () { alert("Поехали!"); }; }; var Toyota = function () { var base = inherit(this, Car); var self = this; }; var ToyotaPrado = function () { var base = inherit(this, Toyota); var self = this; }; var myPrado = new ToyotaPrado(); myPrado.Drive();
Полиморфизм
Для осуществления полиморфизма воспользуемся так же глобальной функцией
inherit
.За неимением в JavaScript чего либо подобного
virtual
и override
, нам опять же необходимо придерживаться правилу:- Все виртуальные свойства/методы должны быть публичными, иначе мы никак не сможем переопределить их. Иными словами все публичные свойства/методы класса виртуальны.
var Car = function (motor) { var self = this; self.Drive = function () { alert("Включаем " + motor + " мотор. Поехали!"); }; }; var Toyota = function (motor) { var base = inherit(this, Car, [motor]); var self = this; self.Drive = function () { base.Drive(); alert("Причем на Toyota."); }; }; var ToyotaPrado = function (model) { var base = inherit(this, Toyota, ["бензиновый 4,0 л"]); var self = this; self.Drive = function () { base.Drive(); alert("А теперь наш Prado " + model + " газ в пол!"); }; }; var myToyota = new Toyota("неизвестный"); var myPrado = new ToyotaPrado("Premium"); alert("Запуск метод: myToyota.Drive()"); myToyota.Drive(); alert("Запуск метод: myPrado.Drive()"); myPrado.Drive();
Один из достоинств NoPrototype стиля - нативная поддержка браузером (если закрыть глаза на метод inherit) или отсутствие зависимости от фреймфорка (JQuery и т.д.), а также реализация инкапсуляции.
как я понимаю, смотря на дату выпуска статий и по отсутствию коментов этим методом не кто не восхетился. а вроде у этого метода есть какойта шарм.
ОтветитьУдалитьНе так просто наткнуться на эту статью в интернете, где все завалено прототипами. Год назад начал писать на JS, сам перешел из C#, очень не хватало нормального ООП. Частично пришел к описанным в статье методам. А сейчас вот случайно увидел эту публикацию. Автор, большой Вам респект и спасибо за статью! Идея классная, обязательно возьму на вооружение.
ОтветитьУдалитьP.S.: картинка тоже очень порадовала :)))
Пользуюсь этим методом, с прототипами что-то не хочется возиться.
ОтветитьУдалитьЭтот комментарий был удален администратором блога.
ОтветитьУдалитьВы вводите новичков в JavaScript в большое заблуждение!!!
ОтветитьУдалитьЭто, во-первых, лже-наследование, т.к. его здесь и близко НЕТ - вы просто вызываете функции/свойства от имени нового класса.
Во-вторых,
alert(myPrado instanceof Toyota); //Выдаст false
alert(myPrado instanceof Car); //Выдаст false
В общем, не дурите новичков в JavaScript!!!
Использование же прототипа:
function Car()
{
this._self = 'Car.';
this.drive = function()
{
document.writeln("Вызван Car.drive() from: " + this._self + "
");
}
}
function Audi()
{
//Car.call(this, name); //Только в случае, если надо передать переменные создателю (constructor) superclass'а. К примеру: Car(year)
this._self = 'Audi.';
this.stop = function()
{
document.writeln("Audi.stop()
");
}
}
Audi.prototype = new Car;
function Opel()
{
this._self = 'Opel.';
this.stop = function()
{
document.writeln("Opel.stop()
");
}
}
Opel.prototype = new Car;
function BMW()
{
this._self = 'BMW.';
this.stop = function()
{
document.writeln("BMW.stop()
");
}
}
BMW.prototype = new Car;
var cars = [new Audi(), new Opel(), new BMW()];
for(key in cars)
{
// Убеждаемся что это Car
if(cars[key] instanceof Car)
{
cars[key].drive();
cars[key].stop();
}
}
Наследование - механизм языка, позволяющий описать новый класс на основе уже существующего (родительского, базового) класса (Википедия). NoPrototype подход вполне подходит под описание.
ОтветитьУдалитьДа, наследование используя прототипы немного быстрее работает (сравнение приведено в следующей статье), и использует меньше памяти, НО код становится менее читабельным.
Еще, попробуете реализовать инкапсуляцию наследуя прототипами. Увы не получится.
В добавок, override (переопределение) метода класса становится очень сложным, и опять же не читабельным, к то мы же нужно иметь ссылку на прототип базового класса.
В примере выше, не показана перегрузка метода базового класса, а зря. Так же, нет никакой инкапсуляции. Внешний код может легко повредить логику класса.
Никакого обмана, один из методов реализации ООП.
Для меня NoPrototype подход имеет больше достоинств, чем недостатков.
Спасибо за критику!