Getters, Setters, és szervező felelősség JavaScript

Egyszer volt, hol nem volt,volt egy nyelv úgynevezett C, 1 és ez a nyelv volt valami úgynevezett struct, és lehetett használni, hogy heterogén aggregált adatstruktúrák voltak tagjai. A legfontosabb dolog, amit tudni kell a C-ről, hogy ha van egy currentUser nevű struktúrád, és egy olyan tagod, mint id, és valami ilyesmit írsz currentUser.id = 42, A C complier ezt rendkívül gyors assembler utasításokká változtatta. Ugyanez a int id = currentUser.idesetében.

az is fontos volt, hogy a structs függvényekre mutató mutatókkal rendelkezzünk, így olyan dolgokat írhatunk, mint a currentUser->setId(42), ha a id beállítást választottuk, és ezt lefordítottuk fast assembler-re is.

és végül, a C programozás nagyon erős kultúrával rendelkezik, amely inkább a “rendkívül gyors”, mint a “gyors”, és így ha egy C programozó figyelmét akartad, meg kellett győződnöd arról, hogy soha nem csinálsz valamit, ami csak gyors, amikor megtehetsz valamit, ami rendkívül gyors. Ez természetesen általánosítás. Biztos vagyok benne, hogy ha körbekérdezünk, végül találkozunk mindkét C programozóval, akik az elegáns absztrakciókat részesítik előnyben a rendkívül gyors kód helyett.

Flathead Dragster

java és javascript

akkor volt egy Java nevű nyelv, amelyet úgy terveztek, hogy böngészőkben fusson, és hordozható legyen mindenféle hardveren és operációs rendszeren, és az egyik célja az volt, hogy a C programozókat Java kód írására késztesse a böngészőben, ahelyett, hogy C-t írna, amely egy pluginben élt. Vagy inkább ez volt az egyik stratégiája, a cél az volt, hogy a Sun Microsystems releváns maradjon egy olyan világban, amelyet a Microsoft árucikké tett, de ez a történelemkönyv újabb fejezete.

tehát a Java mögött álló kedves emberek C-szerű szintaxist adtak neki a zárójelekkel, az utasítás/kifejezés dichotómiával és a pont jelöléssel. Structok helyett “objektumok” vannak, és az objektumok sokkal többet csinálnak, mint a structok, de a Java tervezői különbséget tettek a currentUser.id = 42 és a currentUser.setId(42) között, és megbizonyosodtak arról, hogy az egyik rendkívül gyors, a másik pedig csak gyors. Vagy inkább, hogy az egyik gyors volt, a másik pedig csak rendben volt a C-hez képest, de a C programozók úgy érezhették, hogy fontos gondolkodást végeznek, amikor eldöntötték, hogy a id közvetlenül a teljesítmény érdekében, vagy közvetetten az elegancia és a rugalmasság érdekében kell-e elérni.

a történelem azt mutatja, hogy ez volt a helyes módja egy új nyelv eladásának. A történelem azt is megmutatta, hogy a tényleges teljesítménykülönbség szinte mindenki számára irreleváns volt. A teljesítmény csak egyelőre, a kód rugalmassága örökre szól.

nos, kiderült, hogy a Sunnak igaza volt abban, hogy a C programozókat a Java használatára késztette (rajtam működött, a Codewarriort és a Lightspeed C-t elhagytam), de tévedett a Java böngészőkben történő használatával kapcsolatban. Ehelyett az emberek elkezdtek egy másik nyelvet, a JavaScript – et használni, hogy kódot írjanak a böngészőkben, a Java-t pedig a szervereken.

meg fog lepődni, ha megtudja, hogy a JavaScript-et arra is tervezték, hogy a C programozók kódot írjanak? És hogy a C-szerű szintaxis, a göndör zárójelekkel, az állítás/kifejezés dichotómiával és a pont jelöléssel ment? És bár a JavaScriptnek van valamije, ami olyan, mint egy Java objektum, és olyan, mint egy Smalltalk szótár, meglepő lesz, ha megtudja, hogy a JavaScript is különbséget tesz a currentUser.id = 42 és a currentUser.setId(42)között? És ez eredetileg az egyik lassú volt, a másik kutya-lassú, de a programozók fontos gondolkodást tudtak tenni arról, hogy mikor optimalizálják a teljesítményt, és mikor kell huhogni a programozó józanságáról?

nem, nem lep meg, ha megtudod, hogy ugyanúgy működik, mint a C, ugyanúgy, mint a Java kinda-sort, mint a C, és pontosan ugyanezen okból. És az OK már nem számít.

Frink professzor a Java-n

a közvetlen hozzáférés problémája

nagyon hamar, miután az emberek elkezdtek dolgozni a Java-val, megtudták, hogy a példányváltozók közvetlen elérése szörnyű ötlet volt. A JIT fordítói a currentUser.id = 42 és a currentUser.setId(42) közötti teljesítménykülönbséget szinte senki számára nem relevánsra szűkítették, és a currentUser.id = 42 vagy int id = currentUser.id kód használata rendkívül rugalmatlan volt.

az ilyen műveleteket nem lehetett olyan átfogó aggályokkal díszíteni, mint a naplózás vagy az érvényesítés. Nem tudta felülbírálni a id beállításának vagy megszerzésének viselkedését egy alosztályban. (A Java programozók szeretik az alosztályokat!)

közben a JavaScript programozók is írtak currentUser.id = 42, és végül ők is rájöttek, hogy ez egy szörnyű ötlet. A változás egyik katalizátora az ügyféloldali JavaScript alkalmazások keretrendszereinek érkezése volt. Tegyük fel, hogy van egy nevetségesen egyszerű személy osztály:

és ugyanilyen nevetséges nézet:

minden alkalommal, amikor frissítjük a személy osztályt, emlékeznünk kell arra, hogy rajzolja át a nézetet:

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

miért számít ez?

Nos, ha nem tudja ellenőrizni, hogy bizonyos felelősségeket hol kezelnek, akkor nem igazán tudja megszervezni a programját. Az alosztályok, a módszerek, a keverők és a dekorátorok technikák: lehetővé teszik, hogy kiválasszák, melyik kód melyik funkcióért felelős.

és ez az egész dolog a programozásban: a funkcionalitás megszervezése. A közvetlen hozzáférés nem teszi lehetővé a megszerzés és beállítás tulajdonságaihoz kapcsolódó funkciók rendezését, hanem arra kényszeríti a megszerzést és beállítást végző kódot, hogy a megszerzés és beállítás bármi másért is felelős legyen.

mágneses Magmemória

get and set

nem tartott sokáig, amíg a JavaScript könyvtár szerzői kitalálták, hogyan lehet ezt megszüntetni egy getés set módszerrel. Lecsupaszítva a puszta lényegre szemléltető célokra, ezt írhatnánk:

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

az új Model superclass manuálisan kezeli lehetővé teszi az objektumok hallgatni a get és set módszerek egy modellen. Ha hívják őket, a” hallgatók”értesítést kapnak a .notifyAll módszerrel. Ezt használjuk a PersonView hallgassa meg a Person és hívja a saját .redraw metódust, amikor egy tulajdonságot a .set metódussal állít be.

így tudunk írni:

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

és nem kell hívnunk currentUserView.redraw(), mert a beépített értesítés.setnem nekünk.

más dolgokat is tehetünk a .getés .set, természetesen. Most, hogy ezek módszerek, díszíthetjük őket naplózással vagy érvényesítéssel, ha úgy döntünk. A módszerek rugalmasabbá és nyitottabbá teszik a kódunkat. Például használhatunk egy ES-t.később dekoratőr hozzá naplózási tanácsot .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')}`; }};

mivel nem tehetünk semmi ilyesmit közvetlen tulajdonhoz való hozzáféréssel. Az ingatlanhozzáférés közvetítése a módszerekkel rugalmasabb, mint a tulajdonokhoz való közvetlen hozzáférés, és ez lehetővé teszi számunkra a programunk megszervezését és a felelősség megfelelő elosztását.

Megjegyzés: Az összes ES.a későbbi osztályú dekorátorok a vanília ES 6 kódban szokásos funkciókként használhatók. A @after(LogSetter, 'set') class Person extends Model {...} helyett egyszerűen írjon const Person = after(LogSetter, 'set')(class Person extends Model {...})

technikák

getterek és szetterek javascript-ben

a getterekkel és a setterekkel kapcsolatos probléma jól ismert volt, és a JavaScript evolúciója mögött álló stewardok egy speciális módszer bevezetésével reagáltak arra, hogy a közvetlen tulajdonhozzáférést egyfajta módszerré alakítsák. Így írnánk a Person osztályunkat a “getters” és a “setters” használatával:”

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

amikor egy getkulcsszóval rendelkező metódust vezetünk be, egy gettert határozunk meg, egy metódust, amelyet akkor hívunk meg, amikor a kód megpróbál olvasni a tulajdonságból. Amikor pedig egy set metódust vezetünk be, akkor egy szettert definiálunk, egy metódust, amelyet akkor hívunk meg, amikor a kód megpróbál írni a tulajdonságra.

a Gettereknek és a szettereknek valójában nem kell semmilyen tulajdonságot olvasniuk vagy írniuk, bármit megtehetnek. De ebben az esszében arról fogunk beszélni, hogy felhasználjuk őket a tulajdonhoz való hozzáférés közvetítésére. Getterekkel és szetterekkel írhatunk:

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

és minden még mindig úgy működik, mintha a currentUser.set('first', 'Ragnvald') a .setstílusú kóddal írtuk volna.

Getterek és szetterek lehetővé teszik számunkra, hogy a módszerek használatának szemantikája legyen, de a közvetlen hozzáférés szintaxisa.

Keypunch

az after combinator, amely képes kezelni getterek és szetterek

Getterek és szetterek első pillantásra úgy tűnik, hogy egy mágikus kombinációja ismerős szintaxis és erőteljes képessége meta-program. A getter vagy a szetter azonban nem a szokásos értelemben vett módszer. Tehát nem díszíthetünk egy szettert pontosan ugyanazzal a kóddal, amelyet egy közönséges módszer díszítésére használnánk.

a .setmódszerrel közvetlenül hozzáférhetünk a Model.prototype.set – hez, és egy másik funkcióba csomagolhatjuk. Így működnek a dekoratőreink. De nincs Person.prototype.first módszer. Ehelyett van egy tulajdonságleíró, amelyet csak a Object.getOwnPropertyDescriptor() használatával tudunk ellenőrizni, és frissíteni a Object.defineProperty()használatával.

emiatt a na 6ctiven after a fent megadott dekorátor nem fog működni gettereknél és szettereknél.2. egyfajta dekoratőrt kell használnunk a módszerekhez, egy másikat a getterekhez, egy harmadikat pedig a szetterekhez. Ez nem hangzik szórakoztatónak, ezért módosítsuk a after kombinátorunkat, hogy egyetlen funkciót használhassunk metódusokkal, getterekkel és szetterekkel:

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

most már írhatunk:

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

most leválasztottuk a hallgatók értesítésére szolgáló kódot az értékek lekérésére és beállítására szolgáló kódtól. Ami egy egyszerű kérdést vet fel: ha a hallgatókat nyomon követő kód már leválasztva van a Model – ben, miért ne lehetne az értesítések kiváltására szolgáló kód ugyanabban az entitásban?

ennek néhány módja van. Univerzális mixint fogunk használni ahelyett, hogy ezt a logikát szuperosztályba töltenénk:

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

Ez lehetővé teszi számunkra, hogy írjunk:

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

mit tettünk? Beépítettük a gettereket és a szettereket a kódunkba, miközben fenntartottuk azt a képességet, hogy díszítsük őket hozzáadott funkciókkal, mintha rendes módszerek lennének.

Ez egy győzelem a kód lebontására. És ez arra utal, hogy valamit gondolni: ha minden van a módszerek, akkor arra ösztönzik, hogy nehézsúlyú szuperosztályok. Ezért olyan sok keretrendszer kényszerít arra, hogy bővítse speciális célú alaposztályait, mint például Model vagy View.

de ha megtalálja a módját, hogy használja mixins és díszíteni módszerek, akkor bontani a dolgokat kisebb darabokra, és alkalmazza őket, ahol szükség van rájuk. Ez abba az irányba vezet, hogy a nehézsúlyú keret helyett könyvtári gyűjteményeket használnak.

összefoglalás

a Getterek és szetterek lehetővé teszik számunkra, hogy fenntartsuk a kódírás régi stílusát, amely úgy tűnik, hogy közvetlenül hozzáfér a tulajdonságokhoz, miközben ténylegesen közvetíti ezt a hozzáférést módszerekkel. Óvatosan frissíthetjük szerszámainkat, hogy lehetővé tegyük számunkra, hogy díszítsük gettereinket és szettereinket, elosztva a felelősséget, ahogy jónak látjuk, és felszabadítva minket a nehézsúlyú alaposztályoktól való függőségtől.

(beszéljétek meg a Hacker News)

Post Scriptum

Ez a bejegyzés használ hallgat ingatlan szetterek ürügyként, hogy megvitassák a getter és szetter mechanizmusok, valamint a módját, hogy díszíteni őket, hogy tudjuk szervezni kód körül aggodalmak.

természetesen a változások kifejezett értesítéssel történő terjesztése nem az egyetlen módja annak, hogy olyan kódot szervezzünk, amelynek kezelnie kell az adatváltozástól való függőségeket. Ez túlmutat ezen a poszton, hogy megvitassák a sok alternatíva, de az olvasók azt javasolta feltárása Object.figyelje meg és dolgozzon változatlan adatokkal.

repülő csészealjak mindenkinek

még egy dolog

a Rubyisták gúnyolódnak:

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

a Rubyisták a beépített osztálymódszert használnák attr_accessor, hogy írjon nekünk. Szóval csak viccből írunk egy lakberendezőt, aki gettereket és szettereket ír. A nyers értékeket egy attributes térkép tárolja:

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

most már írhatunk:

attrAccessor az ingatlannevek listája, és egy osztály dekoratőrét adja vissza. Minden tulajdonsághoz egy sima getter vagy setter függvényt ír, és az összes definiált tulajdonság a .attributes hash-ban tárolódik. Ez nagyon kényelmes a sorosításhoz vagy más tartóssági mechanizmusokhoz.

(ez triviális is, hogy aattrReader ésattrWriter funkciók segítségével ezt a sablont. Csak ki kell hagynunk a set írásakor attrReader és kihagyjuk a get írásakor attrWriter.)

  1. volt még egy nyelv, amit BCPL-nek hívtak, és mások előtte, de a történetünknek valahol el kell kezdődnie, és a C. ++ – val kezdődik

  2. sem a mixin recept, amelyet a korábbi bejegyzésekben, például az ES használatával fejlesztettünk ki.később dekorátorok mixinek. Meg lehet javítani, hogy adjunk egy speciális eset getters, szetterek, és egyéb aggályok, mint a munka POJOs. Például Andrea Giammarchi univerzális Mixinje.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.