Getry, Setry a Organizační Odpovědnost v Javascriptu

Kdysi dávno, tam byl jazyk s názvem C,1 a tento jazyk měl něco, co se nazývá struct, a můžete ji použít, aby se nestejnoměrně agregované datové struktury, které měli členové. Hlavní věc, vědět o C je, že když máte struct currentUser, a člen jako id a ty napsat něco jako currentUser.id = 42, C complier obrátil to do extrémně rychlý assembler instrukce. Stejné pro int id = currentUser.id.

Také důležité bylo, že jste mohli mít ukazatele na funkce v structs, takže můžete psát věci jako currentUser->setId(42) pokud byste raději, aby se nastavení id funkce, a to byl také přeložen do rychle assembler.

A konečně, programování C má velmi silnou kulturu raději „extrémně rychlý“ jen „rychle,“ a tak pokud jste chtěli C programmer ‚ s pozornost, aby se ujistil, že jste nikdy udělat něco, co je jen rychle, když můžete dělat něco, co je velmi rychlý. To je samozřejmě zobecnění. Jsem si jistý, že zeptáme-li se kolem, budeme nakonec se setkají i C programátory, kteří dávají přednost elegantní abstrakce extrémně rychlý kód.

Flathead Dragster

java a javascript

Pak tam byl jazyce Java, a to byl navržen tak, aby spustit v prohlížeči, a být přenosné na všechny druhy hardware a operační systémy, a jedním z jeho cílů bylo získat C programátorům psát kód v jazyce Java v prohlížeči namísto psaní C, který žil v plugin. Nebo spíše to byla jedna z jejích strategií, cílem bylo, aby Sun Microsystems zůstal relevantní ve světě, který Microsoft komoditizoval,ale to je další kapitola knihy historie.

milí lidé za Java dal to C-like syntaxe s rovnátka a výrok/vyjádření dichotomie a tečka notace. Mají „objekty“ místo structs, a objekty mají mnohem víc, než se na structs, ale Java je návrháři dělal rozdíl mezi currentUser.id = 42currentUser.setId(42), a ujistili se, že jeden byl extrémně rychlý a druhé bylo jen rychle. Nebo spíše, že jeden byl rychlý, a druhý byl jen ok v porovnání s C, ale C programátoři mohli cítit jako by dělali důležitou myšlení při rozhodování o tom, zda id měly by být přímo přístupné pro výkon nebo nepřímo přístupné pro eleganci a flexibilitu.

historie ukázala, že to byl správný způsob, jak prodat nový jazyk. Historie také ukázala, že skutečné rozlišení výkonu bylo irelevantní pro téměř všechny. Výkon je jen prozatím, flexibilita kódu je navždy.

No, ukázalo se, že Slunce bylo přímo o získání C programátoři používat Java (vyšlo to na mě, vykašlal jsem se na CodeWarrior a Lightspeed C), ale mýlil pomocí Java v prohlížečích. Místo toho lidé začali používat jiný jazyk nazvaný JavaScript pro psaní kódu v prohlížečích a používali Java pro psaní kódu na serverech.

překvapí vás, když zjistíte, že JavaScript byl také navržen tak, aby přiměl programátory C psát kód? A že to šlo se syntaxí podobnou C s kudrnatými závorkami, dichotomií výroku / výrazu a tečkovou notací? A i když JavaScript je věc, která je trochu jako Java objekt, a trochu jako Smalltalk slovník, bude to překvapit vás naučit, že JavaScript má také rozdíl mezi currentUser.id = 42currentUser.setId(42)? A to původně, jeden byl pomalý, a druhý pes-pomalu, ale programátoři mohli udělat důležité přemýšlet o tom, kdy optimalizovat pro výkon, a když se dát sranda o programátor zdravý rozum?

ne, nepřekvapí vás, když zjistíte, že funguje trochu jako C stejným způsobem, jako Java kinda-sort funguje jako C, a to ze stejného důvodu. A na důvodu už opravdu nezáleží.

Profesor Frink na Java

problém s přímým přístupem

Velmi brzy poté, co lidé začali pracovat s Java v rozsahu, které se dozvěděl, že přímo přístup k proměnné instance byl hrozný nápad. JIT překladače snížil výkon rozdíl mezi currentUser.id = 42currentUser.setId(42) téměř nic pro nikoho, a kód pomocí currentUser.id = 42 nebo int id = currentUser.id byla velmi nepružná.

neexistoval žádný způsob, jak ozdobit takové operace průřezovými obavami, jako je protokolování nebo validace. Nelze přepsat chování nastavení nebo získání id v podtřídě. (Java programátoři milují podtřídy!)

mezitím programátoři JavaScriptu také psali currentUser.id = 42 a nakonec také zjistili, že to byl hrozný nápad. Jedním z katalyzátorů změny byl příchod rámců pro aplikace JavaScript na straně klienta. Řekněme, že máme neuvěřitelně jednoduchý člověk třída:

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

A stejně směšné zobrazení:

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

Pokaždé, když jsme se aktualizovat osoba, třída, musíme mít na paměti, překreslení zobrazení:

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

proč na tom záleží?

Pokud nemůžete kontrolovat, kde jsou řešeny určité povinnosti, nemůžete svůj program opravdu uspořádat. Podtřídy, metody, mixiny a dekoratéry jsou techniky: to, co umožňují, je výběr, který kód je zodpovědný za jakou funkčnost.

a to je celá věc o programování: organizování funkčnosti. Přímý přístup neumožňuje organizovat a funkce spojené s získání a nastavení vlastností, nutí kód dělá získání a nastavení také být zodpovědný za něco jiného, spojené s získání a nastavení.

Magnetické Jádro Paměti

nastavení

To netrvalo dlouho pro JavaScript knihovna autoři se zjistit, jak, aby to jít pryč pomocí getset metoda. Svlékl se na holé základy pro ilustrativní účely, mohli bychom to napsat:

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()) }}

Náš nový Model nadtřídy manuálně spravuje umožňuje objekty poslouchat getset metody na modelu. Pokud jsou volány, jsou „posluchači“ upozorněni metodou .notifyAll. Používáme to, aby PersonView poslouchat Person a zavolat své vlastní .redraw metoda, kdy je vlastnost nastavena pomocí .set metoda.

takže můžeme psát:

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

A nemusíme volat currentUserView.redraw(), protože oznámení zabudované do .set dělá to pro nás.

můžeme dělat jiné věci s .get.set, samozřejmě. Nyní, když se jedná o metody, můžeme je ozdobit protokolováním nebo ověřením, pokud se rozhodneme. Díky metodám je náš kód flexibilní a otevřený rozšíření. Můžeme například použít ES.později dekoratér přidat protokolování radu .set:

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')}`; }};

zatímco s přímým přístupem k vlastnostem nemůžeme nic takového udělat. Zprostředkování přístupu k vlastnostem metodami je flexibilnější než přímý přístup k vlastnostem, což nám umožňuje organizovat náš program a správně distribuovat odpovědnost.

Poznámka: Všechny ES.pozdější dekorátory třídy mohou být použity v kódu vanilla ES 6 jako běžné funkce. Místo @after(LogSetter, 'set') class Person extends Model {...} jednoduše napsat const Person = after(LogSetter, 'set')(class Person extends Model {...})

Techniky

getry a setry v javascriptu

problém s getry a setry byl dobře pochopen, a stevardů za JavaScript, vývoj reagoval tím, že zavádí zvláštní způsob, jak proměnit přímý přístup k vlastnosti na druh metody. Zde je návod, jak napsat naši Person třídu pomocí „getters“ a “ setters:“

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}`; }};

Když jsme předmluva metoda s klíčovým slovem get, jsme se definovat getter, metoda, která bude volána tehdy, když se kód pokusí číst z nemovitosti. A když předmluvíme metodu s set, definujeme setr, metodu, která bude volána, když se kód pokusí zapsat do vlastnosti.

Getters a setters nemusí ve skutečnosti číst ani psát žádné vlastnosti, mohou dělat cokoli. Ale v této eseji budeme hovořit o jejich použití ke zprostředkování přístupu k majetku. S getry a setry, můžeme napsat:

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

A vše stále funguje stejně, jako kdyby napsali jsme currentUser.set('first', 'Ragnvald').set-style kód.

Gettery a settery nám umožňují mít sémantiku použití metod, ale syntaxi přímého přístupu.

Keypunch

po combinator, který zvládne getry a setry

Getry a setry na první pohled zdát být kouzelná kombinace známých syntaxe a silnou schopnost meta-programu. Getter nebo setr však není metoda v obvyklém smyslu. Takže nemůžeme ozdobit setr pomocí přesně stejného kódu, který bychom použili k ozdobení běžné metody.

pomocí metody .set bychom mohli přímo přistupovat k Model.prototype.set a zabalit ji do jiné funkce. Tak pracují naši dekoratéři. Neexistuje však žádná metoda Person.prototype.first. Místo toho, tam je vlastnost deskriptor můžeme pouze pozorovat pomocí Object.getOwnPropertyDescriptor() a aktualizovat pomocí Object.defineProperty().

z tohoto důvodu, naivní after dekoratér výše uvedené nebude fungovat pro getry a setry.2 museli bychom použít jeden druh dekoratéra pro metody, další pro gettery a třetí pro settery. To nezní jako legrace, tak se pojďme změnit naše after combinator, takže můžete použít jednu funkci s metodami, getry a setry:

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; }

Teď můžeme napsat:

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}`; }};

Máme nyní oddělena kód pro oznámení posluchačů z kódu pro získání a nastavení hodnoty. Což vyvolává jednoduchou otázku: Pokud je kód, který sleduje posluchače, již oddělen v Model, proč by kód pro spouštění notifikací neměl být ve stejné entitě?

existuje několik způsobů, jak to udělat. Použijeme univerzální mixin místo nádivky, že logika do nadtřídy:

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); } }});

To nám umožňuje napsat:

@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}`; }};

co jsme udělali? Do našeho kódu jsme začlenili gettery a settery, při zachování schopnosti ozdobit je přidanou funkčností, jako by to byly běžné metody.

to je výhra pro rozkládání kódu. A to poukazuje na něco, co si myslet o: Když máš metod, jste doporučuje, aby se v těžké váze superclasses. Proto vás tolik rámců nutí rozšířit své speciální základní třídy jako Model nebo View.

ale když najdete způsob, jak používat mixiny a zdobit metody, můžete rozložit věci na menší kousky a aplikovat je tam, kde jsou potřeba. To vede směrem k použití sbírek knihoven namísto rámce těžké váhy.

shrnutí

Getry a setry nám umožňují zachovat dědictví styl psaní kódu, který se zobrazí přímý přístup k vlastnosti, zatímco ve skutečnosti zprostředkování že přístup s metodami. S péčí, můžeme aktualizovat naše nástroje, které nám umožní vyzdobit náš getry a setry, distribuci odpovědnosti, jak uznáme za vhodné a nás osvobodila od závislosti na váze základní třídy.

(diskutovat na Hacker News)

Post Scriptum

Tento příspěvek využívá poslechu majetku tvůrci jako výmluvu, aby diskutovali o získání a nastavení mechanismů, a způsoby, jak ozdobte je tak, že můžeme organizovat kód kolem obavy.

šíření změn prostřednictvím explicitního oznámení samozřejmě není jediným způsobem, jak uspořádat kód, který potřebuje spravovat závislosti na změně dat. Diskutovat o mnoha alternativách je nad rámec tohoto příspěvku, ale čtenáři navrhli prozkoumat objekt.pozorovat a pracovat s neměnnými daty.

Létající talíře pro všechny

další věc,

Rubyists vysmívají:

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

Rubyists by použít vestavěný metody třídy attr_accessor je psát pro nás. Takže jen pro kopy, napíšeme dekoratér, který píše gettery a settery. Nezpracované hodnoty budou uloženy v mapě attributes :

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; }}

Teď můžeme napsat:

@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 má seznam názvů vlastností a vrátí natěrač pro třídu. Zapisuje funkci plain getter nebo setter pro každou vlastnost a všechny definované vlastnosti jsou uloženy v.attributes hash. To je velmi výhodné pro serializaci nebo jiné mechanismy přetrvávání.

(je triviální také pomocí této šablony vytvářet funkce attrReader a attrWriter. Jen musíme vynechat set při psaní attrReader a vynechat get při psaní attrWriter.)

  1. Tam byl také nazýván jazyk BCPL, a jiní před tím, ale náš příběh musí někde začít, a to začíná s C. ↩

  2. Ani mixin recept jsme vyvinuli v předchozí příspěvky, jako Pomocí ES.později malíři jako Mixins. To může být rozšířen přidat speciální případ pro getters, setters a další obavy, jako je práce s POJOs. Například univerzální Mixin Andrea Giammarchi. ↩

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.