JavaScriptのgetter、Setter、Organizing Responsibility

昔々、C、1という言語があり、この言語にはstructというものがあり、それを使用してメンバーを持つ異質に集約されたデータ構造を作ることができました。 Cについて知っておくべき重要なことは、currentUseridcurrentUser.id = 42のようなものを書くと、Cコンパイラはこれを非常に高速なアセンブラ命令に変えたということです。 int id = currentUser.idcurrentUser->setId(42)id関数を設定することを好む場合は、これも高速アセンブラに変換されました。

そして最後に、Cプログラミングは、単に”速い”よりも”非常に速い”を好むという非常に強い文化を持っているので、Cプログラマの注意を望んでい もちろん、これは一般化です。 私たちが周りに尋ねると、最終的には非常に高速なコードよりもエレガントな抽象化を好む両方のCプログラマに会うだろうと確信しています。

Flathead Dragster

java and javascript

その後、Javaと呼ばれる言語があり、ブラウザで実行し、あらゆる種類のハードウェアとオペレーティン むしろ、それはその戦略の一つであり、目標は、Microsoftがコモディティ化していた世界でサン-マイクロシステムズが関連性を維持することでしたが、それは

だから、Javaの背後にある素敵な人々は、中括弧と文/式の二分法とドット表記を持つCのような構文を与えました。 彼らは構造体の代わりに”オブジェクト”を持っており、オブジェクトは構造体よりもはるかに多くのことが行われていますが、Javaの設計者はcurrentUser.id = 42currentUser.setId(42)を区別し、一方が非常に高速で、もう一方が高速であることを確認しました。 むしろ、それは速く、もう一つはCと比較してokでしたが、Cプログラマはidがパフォーマンスのために直接アクセスされるべきか、優雅さと柔軟性のために間接的にアクセスされるべきかどうかを決定するときに重要な思考をしているように感じることができました。

歴史は、これが新しい言語を販売する正しい方法であることを示しています。 歴史はまた、実際のパフォーマンスの区別はほとんど誰にも無関係であったことを示しています。 パフォーマンスは今のところだけで、コードの柔軟性は永遠にあります。さて、SunはCプログラマにJavaを使用させることについて正しいことが判明しました(私はCodeWarriorとLightspeed Cを捨てました)が、ブラウザでJavaを使用することは間違って 代わりに、人々はブラウザでコードを書くためにJavaScriptと呼ばれる別の言語を使用し始め、Javaを使用してサーバー上でコードを書くようになりました。

JavaScriptはCプログラマにコードを書くように設計されていることを学ぶのは驚くでしょうか? そして、それは中括弧、文/式の二分法、およびドット表記を持つCのような構文と一緒に行ったこと? JavaScriptにはJavaオブジェクトのようなものとSmalltalk辞書のようなものがありますが、JavaScriptにもcurrentUser.id = 42currentUser.setId(42)の区別があ そして、それはもともと、一つは遅かった、と他の犬-遅いですが、プログラマは、パフォーマンスのために最適化するときといつプログラマの正気についてやっつけ仕事を与えるために重要な思考を行うことができますか?いいえ、Java kinda-sortがCのように動作するのと同じように、まったく同じ理由でCのように動作することを学ぶことは驚くことではありません。 そして、理由は本当にもう問題ではありません。

JavaのFrink教授

直接アクセスの問題

人々がJavaを大規模に使い始めた直後、インスタンス変数に直接アクセスすることはひどい考え JITコンパイラは、currentUser.id = 42currentUser.setId(42)currentUser.id = 42int id = currentUser.idを使用するコードは非常に柔軟性がありませんでした。

このような操作を、ロギングや検証のような横断的な懸念で飾る方法はありませんでした。 サブクラスでidを設定または取得する動作を上書きすることはできませんでした。 (Javaプログラマはサブクラスが大好き!一方、JavaScriptプログラマもcurrentUser.id = 42を書いていましたが、最終的に彼らもこれがひどい考えであることを発見しました。 変更の触媒の一つは、クライアント側のJavaScriptアプリケーションのためのフレームワークの到着でした。 途方もなく単純なpersonクラスがあるとしましょう:

class Person { constructor (first, last) { this.first = first; this.last = last; } fullName () { return `${this.first} ${this.last}`; }};

同じようにばかげたビュー:

class PersonView { constructor (person) { this.model = person; } // ... redraw () { document .querySelector(`person-${person.id}`) .text(person.fullName()) }}

personクラスを更新するたびに、ビューを再描画することを覚えておく必要があります:p>

const currentUser = new Person('Reginald', 'Braithwaite');const currentUserView = new PersonView(currentUser);currentUserView.redraw();currentUser.first = 'Ragnvald';currentUserView.redraw();

なぜこれが重要なのですか?まあ、特定の責任が処理される場所を制御できない場合は、実際にプログラムを整理することはできません。

サブクラス、メソッド、ミックスイン、デコレータはテクニックであり、それらが可能にするのは、どのコードがどの機能を担当するかを選択することです。

そして、それはプログラミングについての全体のことです:機能を整理します。 直接アクセスでは、プロパティの取得と設定に関連する機能を整理することはできず、取得と設定を実行するコードは、取得と設定に関連する他のも

磁気コアメモリ

get and set

JavaScriptライブラリの作成者がgetsetメソッドを使用してこれを解消する方法を理解するのに時間がかかりませんでした。 説明の目的のために裸の必需品に取り除かれ、我々はこれを書くことができます:

class Model { constructor () { this.listeners = new Set(); } get (property) { this.notifyAll('get', property, this); return this; } set (property, value) { this.notifyAll('set', property, value); return this = value; } addListener (listener) { this.listeners.add(listener); } deleteListener (listener) { this.listeners.delete(listener); } notifyAll (message, ...args) { for (let listener of this.listeners) { listener.notify(this, message, ...args); } }}class Person extends Model { constructor (first, last) { super(); this.set('first', first); this.set('last', last); } fullName () { return `${this.get('first')} ${this.get('last')}`; }};class View { constructor (model) { this.model = model; model.addListener(this); }}class PersonView extends View { // ... notify(notifier, method, ...args) { if (notifier === this.model && method === 'set') this.redraw(); } redraw () { document .querySelector(`person-${this.model.id}`) .text(this.model.fullName()) }}

新しいModelgetset.notifyAllPersonViewPerson.set.redrawメソッ

だから私たちは書くことができます:また、currentUserView.redraw().set

const currentUser = new Person('Reginald', 'Braithwaite');const currentUserView = new PersonView(currentUser);currentUser.set('first', 'Ragnvald');

呼び出す必要はありません。もちろん、.get.setで他のことを行うことができます。 それらがメソッドであるので、選択した場合は、ロギングまたは検証でそれらを飾ることができます。 メソッドは、私たちのコードを柔軟にし、拡張にオープンにします。 たとえば、ESを使用できます。後でデコレータにロギングアドバイスを追加する.set:P>

const after = (behaviour, ...methodNames) => (clazz) => { for (let methodName of methodNames) { const method = clazz.prototype; Object.defineProperty(clazz.prototype, methodName, { value: function (...args) { const returnValue = method.apply(this, args); behaviour.apply(this, args); return returnValue; }, writable: true }); } return clazz; }function LogSetter (model, property, value) { console.log(`Setting ${property} of ${model.fullName()} to ${value}`);}@after(LogSetter, 'set')class Person extends Model { constructor (first, last) { super(); this.set('first', first); this.set('last', last); } fullName () { return `${this.get('first')} ${this.get('last')}`; }};

一方、プロパティに直接アクセスするとそのようなことはできません。 プロパティへのアクセスをメソッドで仲介することは、プロパティに直接アクセスするよりも柔軟性があり、これによりプログラムを整理し、責任を適切に分配することができます。P>

注:すべてのES。後のクラスデコレータは、通常の関数としてバニラES6コードで使用できます。 代わりに@after(LogSetter, 'set') class Person extends Model {...}const Person = after(LogSetter, 'set')(class Person extends Model {...})

テクニック

javascriptのgetterとsetter

getterとsetterの問題セッターはよく理解されており、javascriptの進化の背後にあるスチュワードは、直接プロパティアクセスを一種のメソッドに変える特別な方法を導入するこ ここでは、”getters”と”setters”を使用してPersonクラスを記述する方法を示します:”

class Model { constructor () { this.listeners = new Set(); } addListener (listener) { this.listeners.add(listener); } deleteListener (listener) { this.listeners.delete(listener); } notifyAll (message, ...args) { for (let listener of this.listeners) { listener.notify(this, message, ...args); } }}const _first = Symbol('first'), _last = Symbol('last');class Person extends Model { constructor (first, last) { super(); this.first = first; this.last = last; } get first () { this.notifyAll('get', 'first', this); return this; } set first (value) { this.notifyAll('set', 'first', value); return this = value; } get last () { this.notifyAll('get', 'last', this); return this; } set last (value) { this.notifyAll('set', 'last', value); return this = value; } fullName () { return `${this.first} ${this.last}`; }};

キーワードgetgetget

class Model { constructor () { this.listeners = new Set(); } addListener (listener) { this.listeners.add(listener); } deleteListener (listener) { this.listeners.delete(listener); } notifyAll (message, ...args) { for (let listener of this.listeners) { listener.notify(this, message, ...args); } }}const _first = Symbol('first'), _last = Symbol('last');class Person extends Model { constructor (first, last) { super(); this.first = first; this.last = last; } get first () { this.notifyAll('get', 'first', this); return this; } set first (value) { this.notifyAll('set', 'first', value); return this = value; } get last () { this.notifyAll('get', 'last', this); return this; } set last (value) { this.notifyAll('set', 'last', value); return this = value; } fullName () { return `${this.first} ${this.last}`; }};

メソッドの前にgetgetメソッドの前にset、セッター、コードがプロパティに書き込もうとするときに呼び出されるメソッ

Getterとsetterは、実際にプロパティを読み書きする必要はなく、何でも行うことができます。 しかし、このエッセイでは、プロパティへのアクセスを仲介するためにそれらを使用することにつ Getterとsetterを使用すると、次のように書くことができます。

const currentUser = new Person('Reginald', 'Braithwaite');const currentUserView = new PersonView(currentUser);currentUser.first = 'Ragnvald';

すべてがcurrentUser.set('first', 'Ragnvald').setスタイルのコードで書いたかのように動作します。

Getterとsetterは、メソッドを使用する意味論を持つことができますが、直接アクセスの構文を持つことができます。

Keypunch

getterとsetterを扱うことができるafter combinator

Getterとsetterは、一見したところ、使い慣れた構文とメタプログラムに対する強力な能力の魔法の組み合 しかし、getterまたはsetterは通常の意味でのメソッドではありません。 そのため、通常のメソッドを装飾するために使用するのとまったく同じコードを使用してセッターを装飾することはできません。

.setModel.prototype.setPerson.prototype.firstObject.getOwnPropertyDescriptor()Object.defineProperty()を使用して更新できるプロパティ記述子があります。このため、上記のnaïveafterデコレータはgetterとsetterでは機能しません。2メソッドにはある種類のデコレータ、getterには別の種類のデコレータ、setterには第三のデコレータを使用する必要があります。 それは楽しいようには聞こえませんので、afterコンビネータを変更して、メソッド、ゲッター、セッターで単一の関数を使用できるようにしましょう。

function getPropertyDescriptor (obj, property) { if (obj == null) return null; const descriptor = Object.getOwnPropertyDescriptor(obj, property); if (obj.hasOwnProperty(property)) return Object.getOwnPropertyDescriptor(obj, property); else return getPropertyDescriptor(Object.getPrototypeOf(obj), property);};const after = (behaviour, ...methodNames) => (clazz) => { for (let methodNameExpr of methodNames) { const = methodNameExpr.match(/^(?:(get|set) )(.+)$/); const descriptor = getPropertyDescriptor(clazz.prototype, methodName); if (accessor == null) { const method = clazz.prototype; descriptor.value = function (...args) { const returnValue = method.apply(this, args); behaviour.apply(this, args); return returnValue; }; descriptor.writable = true; } else if (accessor === "get") { const method = descriptor.get; descriptor.get = function (...args) { const returnValue = method.apply(this, args); behaviour.apply(this, args); return returnValue; }; descriptor.configurable = true; } else if (accessor === "set") { const method = descriptor.set; descriptor.set = function (...args) { const returnValue = method.apply(this, args); behaviour.apply(this, args); return returnValue; }; descriptor.configurable = true; } Object.defineProperty(clazz.prototype, methodName, descriptor); } return clazz; }

:

const notify = (name) => function (...args) { this.notifyAll(name, ...args); };@after(notify('set'), 'set first', 'set last')@after(notify('get'), 'get first', 'get last')class Person extends Model { constructor (first, last) { super(); this.first = first; this.last = last; } get first () { return this; } set first (value) { return this = value; } get last () { return this; } set last (value) { return this = value; } fullName () { return `${this.first} ${this.last}`; }};

値を取得および設定するためのコードからリスナーに通知するためのコードを分離しました。 リスナーを追跡するコードがすでにModelで分離されている場合、通知をトリガーするコードが同じエンティティにないのはなぜですか?それを行うにはいくつかの方法があります。

そのロジックをスーパークラスに詰め込むのではなく、ユニバーサルミックスインを使用します。

const Notifier = mixin({ init () { this.listeners = new Set(); }, addListener (listener) { this.listeners.add(listener); }, deleteListener (listener) { this.listeners.delete(listener); }, notifyAll (message, ...args) { for (let listener of this.listeners) { listener.notify(this, message, ...args); } }}, { notify (name) { return function (...args) { this.notifyAll(name, ...args); } }});

これにより、次のように書くこ:div>

@Notifier@after(Notifier.notify('set'), 'set first', 'set last')@after(Notifier.notify('get'), 'get first', 'get last')class Person { constructor (first, last) { this.init(); this.first = first; this.last = last; } get first () { return this; } set first (value) { return this = value; } get last () { return this; } set last (value) { return this = value; } fullName () { return `${this.first} ${this.last}`; }};

私たちは何をしましたか? 私たちは、通常のメソッドであるかのように追加された機能でそれらを飾る能力を維持しながら、コードにgetterとsetterを組み込んでいます。

これはコードを分解するための勝利です。 そして、それは考えるべきことを指しています:あなたが持っているすべてが方法であるとき、あなたはヘビー級のスーパークラスを作る そのため、多くのフレームワークでは、ModelViewような特別な目的の基本クラスを拡張する必要があります。しかし、mixinsを使用してdecorateメソッドを使用する方法を見つけると、物事を小さな部分に分解し、必要な場所に適用することができます。

しかし、 これは、重いフレームワークの代わりにライブラリのコレクションを使用する方向につながります。

summary

Getterとsetterを使用すると、プロパティに直接アクセスするように見えるコードを記述する従来のスタイルを維持しながら、実際にそのアクセ 注意して、getterとsetterを飾ることができるようにツールを更新し、適切と思われるように責任を分散し、ヘビー級の基本クラスへの依存から解放することがで

(discuss on Hacker News)

Post Scriptum

この記事では、getterとsetterのメカニズムを議論するための言い訳として、プロパティセッターを聞いて使用しています。もちろん、明示的な通知を介して変更を伝播することは、データ変更への依存関係を管理する必要があるコードを整理する唯一の方法ではありません。 多くの選択肢について議論するのはこの記事の範囲を超えていますが、読者はオブジェクトを探索することを提案しています。不変データを観察し、操作する。p>

皆のための空飛ぶ円盤

もう一つのこと

Rubyistsは嘲笑:

get first () { return this;}set first (value) { return this = value;}get last () { return this;}set last (value) { return this = value;}

Rubyistsは組み込みのクラスメソッドattr_accessor

rubyistsは組み込みのクラスメソッドを使用しますattr_accessor

rubyistsは組み込みのクラスメソッドを使用しますattr_accessor

get first () { return this;}set first (value) { return this = value;}get last () { return this;}set last (value) { return this = value;}

/div>私たちのためにそれらを書くために。 そのため、キックのためだけに、getterとsetterを書くデコレータを書きます。 生の値はattributesマップに格納されます:Div>

function attrAccessor (...propertyNames) { return function (clazzOrObject) { const target = clazzOrObject.prototype || clazzOrObject; for (let propertyName of propertyNames) { Object.defineProperty(target, propertyName, { get: function () { if (this.attributes) return this.attributes; }, set: function (value) { if (this.attributes == undefined) this.attributes = new Map(); return this.attributes = value; }, configurable: true, enumerable: true }) } return clazzOrObject; }}

今、私たちは書くことができます:

@Notifier@attrAccessor('first', 'last')@after(Notifier.notify('set'), 'set first', 'set last')@after(Notifier.notify('get'), 'get first', 'get last')class Person { constructor (first, last) { this.init(); this.first = first; this.last = last; } fullName () { return `${this.first} ${this.last}`; }};

attrAccessorプロパティ名のリストを取り、クラスのデコレータを返します。 これは、各プロパティのプレーンなgetterまたはsetter関数を書き込み、定義されたすべてのプロパティは.attributesattrReaderattrWritersetgetattrWriter。以前はBCPLなどという言語もありましたが、私たちの話はどこかから始まらなければならず、Cで始まります。↩

  • どちらもmixin以前の記事でesを使用するように進化したレシピです。後にミックスインとしてデコレータ。 これは、getter、setter、およびPojoの操作のようなその他の懸念のための特別なケースを追加するために拡張することができます。 例えば、Andrea GiammarchiのUniversal Mixin。 li

  • コメントを残す

    メールアドレスが公開されることはありません。