JavaScript: стоит ли делать переменные приватными и определять геттеры / сеттеры?

Я хотел бы начать с того, что понимаю, что JavaScript - это бесклассовый язык. Мой опыт работы с Java, C ++ и Objective-C, которые являются классическими языками ООП, поддерживающими классы.

Я занимаюсь веб-разработкой, экспериментирую с JavaScript и изучаю его шаблоны. Прямо сейчас я работаю с шаблоном конструктора, который имитирует классы в JavaScript.

Итак, это мой "практический" класс:

function Car( model, year, miles ) {
    this.model = model;
    this.year = year;
    this.miles = miles;

    var privateVarTest = 10;

    this.getPrivateVarTest = function() {
        return privateVarTest;
    }

    this.setPrivateVarTest = function( value ) {
        privateVarTest = value;
    }
}

Car.prototype.toString = function() {
    return this.model + " is a " + this.year + " model and has " + 
           this.miles + " miles.";
}

var myCar = new Car( "Ford Focus", "2006", "65,000" );
document.getElementById('notepad').innerHTML += '</br> Testing </br>';
document.getElementById('notepad').innerHTML += myCar.toString() + '</br>';
document.getElementById('notepad').innerHTML += myCar.model + '</br>';
document.getElementById('notepad').innerHTML += myCar.getPrivateVarTest() + '</br>';
myCar.setPrivateVarTest( 20 );
document.getElementById('notepad').innerHTML += myCar.getPrivateVarTest() + '</br>';

Теперь мне нравится использовать prototype способ определения функций, поскольку он не создает новую версию функции для каждого созданного Car объекта. Однако в классических языках ООП мы создаем переменные private и создаем public функций / методов для установки и получения этих переменных по мере необходимости.

Поскольку JavaScript является бесклассовым, нет private или public ключевых слов для этого использования, поэтому я подумал, что поэкспериментирую с методом "подделки" переменной private, и когда обнаружил, что использование var вместо this существенно делает его недоступным вне constructor, но я смог определить геттеры и сеттеры, которые позволили бы мне это сделать.

Теперь, наконец, к моему вопросу, извините за долгий конец. В соответствии с рекомендациями опытных программистов на JavaScript: сделайте все переменные private в соответствии со стандартами других языков ООП и установите геттеры и сеттеры (которые не могут быть прототипированы для принудительного создания каждого объекта), или избегайте их, насколько это возможно, поскольку ключевое слово this в основном позволяет вам получать и устанавливать все, что вы хотите, и ТОЛЬКО использовать private для жесткого кодирования некоторых внутренних данных, необходимых для класса?

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


person defaultNINJA    schedule 05.12.2012    source источник


Ответы (3)


Общие ООП

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

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

Кроме того, когда свойства открываются, единственная причина для раскрытия с помощью метода заключается в том, что вы либо не можете просто раскрыть свойство из-за языковых ограничений (Java), либо потому, что при изменении этого свойства должна произойти некоторая проверка или уведомление. Простое использование методов в стиле Java-bean, которые не делают ничего, кроме фактического изменения или возврата свойств, ничего не делает для сохранения инкапсуляции. Вы могли бы просто сделать собственность общедоступной, если можете.

Но настоящая проблема с желанием получить / установить все волей-неволей из любого места в том, что вы в основном просто написали связанный процедурный код и назвали его ООП. У вас все еще есть длинный извилистый ряд вещей, о которых можно рассуждать только в терминах того, что одно происходит за другим. Идея ООП состоит в том, чтобы избежать этой длинной извилистой цепочки спагетти, чтобы вы могли больше рассматривать свою архитектуру с точки зрения более крупных конструкций, которым принадлежат определенные домены, взаимодействующие друг с другом в ключевых точках. Без этого вы, возможно, уменьшите количество спагетти, по крайней мере, классифицируя свои функции по пространствам имен, чтобы было легче знать, где искать материал, но вы на самом деле не используете ключевые преимущества, которые ООП может предоставить вашей архитектуре.

Реальное значение переменных private или, в случае JS, local constructor / factory-closur - это сигнал о намерении. Если он обнажен, значит, что-то внешнее действительно должно его изменить. Если это не так, значит, вы дали понять, что var - это только бизнес объекта.

JS OOP

Мой совет - забыть об эмуляции классов в JS. Это совершенно не нужно. Прототипы изящны и просты, если вы их поймете. Думайте о свойстве прототипа конструктора как о некоем резервном объекте. Если вы вызываете метод для экземпляра, которого у него нет, следующим шагом будет проверка свойства объекта прототипа конструктора экземпляра. Если у этого объекта его нет, то проверяется объект-прототип его конструктора и так далее, пока вы, наконец, не дойдете до прототипа основного конструктора Object.

Это из-за этого процесса поиска, что вы можете добавлять новые методы в конструктор на лету и все экземпляры «наследовать» его после того, как они были построены, но на самом деле это не столько наследование, сколько резервный процесс.

Наследование в JS - это просто глупо. Это не значит, что вы должны делать много этого. Длинные цепочки каскадного наследования считаются антипаттерном на любом языке по уважительной причине, и из-за того, как работает процесс обратного вызова, он также может действительно убить производительность, если вы забиваете объект вызова через 18 уровней прототипов для каждая мелочь в JS. Я бы предпочел составные объекты наследованию, и когда наследование кажется более разумным вариантом, проверяйте себя каждый раз, когда у вас возникает соблазн наследовать через более чем 2-3 звеньев прототипа в цепочке.

О, и еще одна хитрость JS, на которую нужно обратить внимание на локальные переменные экземпляра в конструкторах как на частные свойства: на самом деле это просто правила закрытия JS в контексте области действия функции в действии. Методы, объявленные в прототипе или вне функции конструктора, не могут получить доступ к этим внутренним переменным. Функции-конструкторы, вызываемые с помощью ключевого слова new, изменяют правила доступа this, и они оставляют экземпляр позади, но в остальном выполняются JS-функциями другими способами.

Другие разновидности сумасшедшего, но также безумно мощного, стоящего понимания в JS OOP, - это методы apply, call и now bind. Я склонен рассматривать их скорее как вещи, которые вы хотели бы видеть на фабрике, но они очень эффективны.

После того, как вы освоите JS OOP, начните понимать JS с функциональной точки зрения, и вы обнаружите, что в нем действительно мощная комбинация 1-2 удара. Мы можем делать что угодно очень легко и с минимумом кода на JS. Компромисс дизайна - это производительность (с которой современные JIT-компиляторы справляются на удивление хорошо) и то, что это дает вам достаточно веревки, на которой можно повеситься. Я предпочитаю веревку. Сам линчевание - это не весело, но это часть процесса обучения / развития лучших инстинктов, который в результате происходит намного быстрее и в долгосрочной перспективе приводит к более удобному в сопровождении коду. В то время как Java в основном заставляет внедрять ООП, но из-за чрезмерного протекционизма по отношению к разработчикам, которые делают себе глупые вещи, приводит к широкому принятию сообществом практик, которые полностью противоречат сути ООП.

Краткая версия:

  • Перестаньте получать / настраивать много, если вы это сделаете, независимо от языка. Это в первую очередь резко снижает выигрыш от внедрения ООП.
  • Прототипы действительно просты, элегантны и мощны. Повозитесь с ними. Узнай их. Но будьте осторожны. Классы могут начать казаться архаичными, неуклюжими и перегруженными по сравнению (хотя, честно говоря, совершенно необходимо для неинтерпретируемых языков).
  • Чтобы JS работал на вас, изучите дерьмо самостоятельно, с каким бы его аспектом вы ни столкнулись. Награды в виде чистой элегантной языковой силы более чем оправдывают потраченное время. JS ближе к Scheme, чем языки, которые вы перечислили, поскольку они знакомы, так что это странно, но это не странно произвольно или без учета принципов дизайна, и доминирующий успех JS в веб-интерфейсе не случаен, независимо от того, что люди говорят всем, что мы "застряли" с этим "вы бы поверили.
  • Полное раскрытие: я не люблю Java.

Обновлять:

Ключевое слово es6 class практически ничего не меняет в ООП в JS. Это 100% синтаксический сахар. ИМО, использование слова «класс» не приносит новичкам никакой пользы, но есть преимущества / недостатки для всех трех стилей конструктора / создания объекта и создания экземпляра объекта в JS, и все они заслуживают того, чтобы их знать / понимать. Эти три подхода - это функции как конструкторы, Object.create, а теперь и ключевое слово class.

person Erik Reppen    schedule 05.12.2012
comment
Это фантастический ответ. Спасибо, Эрик. - person Jordan; 05.12.2012
comment
Спасибо, Эрик, за этот очень хорошо продуманный и продуманный ответ! Недавно я начал разработку в Web с помощью обычных языков, PHP, HTML, XML, SQL (для хранения и извлечения данных) и, конечно же, JavaScript. Изначально я использовал JavaScript только в крайнем случае, но я заметил, что JavaScript (особенно с jQuery) обладает огромными возможностями для создания действительно хороших приложений. Еще раз спасибо, Эрик! - person defaultNINJA; 06.12.2012
comment
jQuery является мощным средством уменьшения / нормализации мусора DOM API, но я стараюсь похоронить его внутри объектов с интерфейсами, более доступными для веб-разработчиков, не специализирующихся на пользовательском интерфейсе. Когда у вас будет возможность, попробуйте понять, как это работает под капотом. Сначала это выглядит беспорядком, но с точки зрения фабрики объектов JavaScript происходит кое-что действительно умное с точки зрения сохранения легкости объектов. Я определенно предпочел бы JSON XML каждый раз, когда у вас есть выбор форматов данных. JSON намного компактнее, а его JavaScript так легко читать / использовать на клиенте. - person Erik Reppen; 06.12.2012

Нам нужно осознавать нашу тенденцию к тому, чтобы каждый новый язык, который мы изучаем, вел себя так же, как последний язык, который мы выучили. Или первое. И так далее. У Дугласа Крокфорда есть отличный (хотя и немного устаревший) разговор о Google, в котором он размышляет: «Javascript - единственный язык, который я знаю, люди считают, что им не нужно учить его, прежде чем использовать». Этот доклад ответит на множество вопросов, о которых вы даже не подозревали, включая тот, который вы задали здесь.

Нет ничего плохого в написании сеттеров и геттеров. Работа для сохранения рассудка редко приносит вред. Когда вы говорите на JS, у вас может быть «C-акцент», но, по крайней мере, вы будете ясны в своих намерениях для всех, кто читает ваш код.

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

var self = this;

Я избегаю использования прототипа, за исключением особых случаев, включая мои методы объекта в объем объявления.

function MyClass(_arg){
    var self = this;
    this.arg = _arg;

    this.returnArg = function(){
         return self.arg; 
    }
}

var foo = new MyClass("bar");
foo.returnArg();  //"bar"
person cratervale    schedule 05.12.2012
comment
Итак, я понимаю, чем может быть полезно сохранение ссылки на исходный this, но как это помогает инкапсулировать данные и предотвращать доступ к определенным аспектам класса? Вы и другие программисты JavaScript не беспокоитесь об ограничении доступа к данным при использовании классов? - person defaultNINJA; 05.12.2012
comment
Инкапсуляция связана не с ограничением доступа к данным, а с привязкой данных домена таким образом, чтобы только сам объект принимал решение об изменении чего-либо. Это моя основная проблема с оптовыми покупками / настройками. В идеале в большинстве случаев объекты запрашивают друг у друга какие-то действия. Они не говорят друг другу, что делать со своими внутренними данными. - person Erik Reppen; 05.12.2012
comment
^ Я должен уточнить. Ограничение доступа к данным действительно происходит. Просто научитесь видеть в этом побочный эффект, а не цель, чтобы разобраться с ООП. Люди все время скрещивают свои куры и яйца с этим. - person Erik Reppen; 05.12.2012
comment
И я всегда буду +1 любому, кто считает, что JS нужно изучать как отдельную парадигму, как и любой другой язык. Однако я не виню людей в том, что их обманывают. C-синтаксис позволяет легко думать о том, чего он не может делать по сравнению с языками, которые выглядят и ощущаются очень похожими на несколько шагов за пределами Hello World. - person Erik Reppen; 05.12.2012
comment
Мой ответ - выделить C-подобный синтаксис для привязки данных к домену в Javascript в качестве доказательства концепции и в предположении, что сам вопрос осведомлен о своей традиционной предвзятости объектно-ориентированного программирования. Особенность Эрика в отношении цели инкапсуляции очень ценится, потому что она подчеркивает, что JS, хотя и способен имитировать средства другого языка, имеет свои собственные. - person cratervale; 05.12.2012
comment
Спасибо всем за ваши ответы. Я действительно изо всех сил стараюсь изучить JavaScript, отсюда и причина моего вопроса. Я знаю, как все делается на одном языке, но хочу использовать JavaScript как JavaScript :-) Главное, на что я обращаю внимание, - это использование шаблонов в JavaScript (из книги, которую я читаю) и попытки понять объектно ориентированный подход. использует JavaScript, или если объектно-ориентированный объект действительно нужен в JavaScript, поскольку на самом деле все является объектом. - person defaultNINJA; 06.12.2012
comment
Все, что является объектом, не устраняет необходимости в ООП. Многие шаблоны проектирования не подходят для JS, поскольку мы можем передавать функции, но многие из них подходят. Кроме того, статья в Википедии о шаблонах проектирования на удивление хороша для обзора. - person Erik Reppen; 06.12.2012

в случае ООП я должен сказать, что на самом деле javascript обеспечивает некоторый уровень oop.

Под этим я подразумеваю, что 4 основных концепции дизайна ООП могут быть реализованы в javascript, хотя он не является сильным и очень хорошо определенным, как в Java или C ++. Давайте проверим эти концепции, и я постараюсь привести примеры для каждой из них.

1- Абстракция: здесь, как я сказал ранее, мы можем понять, почему ООП не очень хорошо определено, как в Java, в Java мы реализуем концепцию абстракции, используя классы, переменные, сопряженные, ... но в javascript Abstraction определяется скорее неявно, в отличие от других языков ООП, таких как Java.

2- Инкапсуляция. Думаю, здесь будет достаточно примера.

function Student (stdName, stdEmail, stdAvg) {
  this.name = theName;
  this.email = theEmail;
  this.avg = stdAvg;
 }

Здесь также, как вы видите, мы определяем концепцию, подобную «классу», с использованием функций. Фактически, если получить тип Student, вы увидите, что это функция.

3,4 - Наследование и полиморфизм.

const Gun = function(soundEffect){
  this.soundEffect = soundEffect;
};

Gun.prototype.fire = function(){
  console.log(this.soundEffect);
};

const DesertEagle = function(color,clipSize){
  this.color = color;
  this.clipSize = clipSize;
};

DesertEagle.prototype = new Gun("pew pew peeeew");

const myWeapon = new DesertEagle("black",15);

myWeapon.fire();

Теперь, чтобы охватить публичный / частный доступ к переменным и функциям, мы должны использовать какую-то технику для реализации такой концепции. проверьте код ниже:

const Student = function(name, stdNumber, avg){
  this.name = name;
  this.stdNumber = stdNumber;
  this.avg = avg;
  var that = this; //NOTE : we need to store a reference to "this" in order for further calls to private members

  this.publicAccess = { // a set of functions and variables that we want as public
    describe: function () {
       console.log(that.name + " : " + that.stdNumber);
    },
    avg: this.avg,
  };

  return this.publicAccess; // return set of public access members
};


const newStd = new Student("john", "123", "3.4");

newStd.describe();
// output: john : 123
console.log(newStd.avg)
// output: 3.4

в ES6 определение класса намного проще, но это всего лишь синтаксический сахар, в основе которого лежит все то же самое.

Надеюсь, это поможет . Я также рекомендую вам эту статью (шаблоны проектирования Javascript), в которой содержится некоторая полезная информация о возможностях avascript и шаблонах проектирования.

примите мои извинения за мой плохой английский.

person N-Alpr    schedule 22.08.2017