[JS] Abstractie van prototyped OO

Pagina: 1
Acties:

  • Victor
  • Registratie: November 2003
  • Niet online
Ik zie steeds meer developers gebruik maken van diverse abstractiemethoden om met OO in JavaScript om te springen. Of het nu om Class.create(), Base.extend() of Object.extend() gaat, het zijn allemaal constructies bedoeld om het gebruik van OO in JavaScript te vereenvoudigen. Ik heb bij het bekijken van deze constructies echter nauwelijks voordelen kunnen vinden, wel nadelen:

- Met de gebrekkige documentatie die vaak hand in hand gaat met JavaScripts kunnen dit soort methoden de overdraagbaarheid tussen developers vermoeilijken.
- In alle gevallen gaat het gebruik van private members verloren.
- In sommige gevallen breekt het de inheritance chain.

Waarom dan toch voor deze aanpak kiezen? Normaal gesproken laat ik het librarywereldje met z'n hypes van korte duur lekker links liggen, maar ik zie nu ook "serieuze" JavaScript developers deze weg inslaan. Ik ben dus wel benieuwd naar de voors en tegens!

Het gaat me hier met name dus om de abstractie van het prototyped OO model. Wrappers/frameworks/libraries rond bijvoorbeeld het DOM begrijp ik wel en gebruik ik zelf ook.

  • Gomez12
  • Registratie: Maart 2001
  • Laatst online: 17-10-2023
Simpele opmerking, gebrekkige documentatie is nergens voor nodig.

Al mijn JS is keurig gedocumenteerd in de code zelf. Er gaat wel een minifyer en gzip compressie en een caching systeem overheen voordat het naar de client gaat, dus die ziet niets van het commentaar.

Maar als je toegang hebt tot de server tref je goed gedocumenteerde JS en CSS aan, JS hoeft dus helemaal niet gebrekkig gedocumenteerd te zijn.
Iemand die slecht gedocumenteerde JS schrijft die schrijft imho ook slecht gedocumenteerde C++ / Java code. De taal heeft er niets mee te maken imho.

Of je moet echt geen server-side coding hebben en je zorgen maken om elke byte de je hosting verlaat. Maar zelfs dan kan je nog met versies en expiry-dates ervoor zorgen dat je terugkerende bezoekers niet elke keer de ongewijzigde JS downloaden...

  • Victor
  • Registratie: November 2003
  • Niet online
Gomez12 schreef op donderdag 12 juni 2008 @ 21:32:
Simpele opmerking, gebrekkige documentatie is nergens voor nodig.
Dat ben ik met je eens en ik besteed zelf ook de nodige tijd aan het documenteren van mijn code. Maar helaas wordt JavaScript nog altijd veel als "erbij" taaltje gezien, dus is dat de eerste plek waar men het schrijven van commentaar overslaat.

Maar zelfs met commentaar blijf ik nog steeds met twee obstakels en geen zichtbare voordelen zitten.

Verwijderd

Victor schreef op donderdag 12 juni 2008 @ 21:19:
- Met de gebrekkige documentatie die vaak hand in hand gaat met JavaScripts kunnen dit soort methoden de overdraagbaarheid tussen developers vermoeilijken.
Dit is een symptoom maar niet een inherent probleem. Als de technieken gemeengoed gaan worden zal dit een steeds kleinere factor zijn.
- In alle gevallen gaat het gebruik van private members verloren.
Dat hoeft niet perse, trouwens. Met gewone 'vanilla' prototype-based inheritance kun je gewoon private members blijven gebruiken. (en dan bedoel ik dus niet het "prototype" framework maar de prototype property) de Class.create() varianten verliezen dat inderdaad wel wat voor mij persoonlijk reden genoeg is om ze niet in mijn eigen projecten te gebruiken. Overigens (en dit is een hele andere discussie) kleven er aan private members in JS ook wel wat nadelen. Ze zijn (a) lastiger te debuggen omdat ze niet zichtbaar zijn bij het inspecten van een object of in de script debugger van Firebug en (b) ze zijn niet of via een omweg te unit-testen.
- In sommige gevallen breekt het de inheritance chain.
Geef eens een voorbeeld? Dat zou namelijk het hele OO-idee onderuit halen.
Waarom dan toch voor deze aanpak kiezen?
Omdat het je code overzichtelijker, compacter en beter onderhoudbaar kan maken. Het kan ook precies het tegenovergestelde doen. Het is maar net hoe je het toepast. Het valt of staat eigenlijk bij je object-model. Ikzelf gebruik eigenlijk alleen maar de bovengenoemde prototypal inheritance en dan zelden meer dan 2 nivo's diep.

Ik snap daarom dus ook niet waarom er zo'n focus is in de JS-wereld op die inheritance technieken. Naar mijn mening zijn er veel belangrijkere patterns die veel meer winst opleveren met minder verwarring (zoals bv het publisher/subscriber pattern).

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 23:43

crisp

Devver

Pixelated

Victor schreef op donderdag 12 juni 2008 @ 21:36:
[...]

Dat ben ik met je eens en ik besteed zelf ook de nodige tijd aan het documenteren van mijn code. Maar helaas wordt JavaScript nog altijd veel als "erbij" taaltje gezien, dus is dat de eerste plek waar men het schrijven van commentaar overslaat.
En daar zou je dan ook de conclusie uit kunnen trekken dat er legio mensen zijn die dan ook liever zien dat het OO-model van javascript meer lijkt op datgene dat ze gewend zijn van andere talen...

Object.extend() is overigens geen abstratie-method maar gewoon een helper method om in 1 keer meerdere prototyped methods toe te kennen (met eventueel een check of een dergelijke method al bestaat)

Intentionally left blank


Verwijderd

Ik ben zelf van mening dat je gewoon de taal moet leren, in plaats van proberen het op een klassiek OO model te laten lijken

Ik vind overigens ook dat je van objecten af moet blijven die niet van jezelf zijn. Nee dus tegen Object.extend. Ik wil gewoon met for (.. in ..) door m'n objecten kunnen blijven loopen, zonder bij elke member te gaan vragen of ie wel van jezelf is

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 23:43

crisp

Devver

Pixelated

Object.extend != Object.prototype.extend ;)

Intentionally left blank


Verwijderd

Daar heb je zeer zeker gelijk in, ik was weer eens wat kort door de bocht :)

  • Victor
  • Registratie: November 2003
  • Niet online
Verwijderd schreef op donderdag 12 juni 2008 @ 23:24:
Dat hoeft niet perse, trouwens. Met gewone 'vanilla' prototype-based inheritance kun je gewoon private members blijven gebruiken. (en dan bedoel ik dus niet het "prototype" framework maar de prototype property) de Class.create() varianten verliezen dat inderdaad wel wat voor mij persoonlijk reden genoeg is om ze niet in mijn eigen projecten te gebruiken. Overigens (en dit is een hele andere discussie) kleven er aan private members in JS ook wel wat nadelen. Ze zijn (a) lastiger te debuggen omdat ze niet zichtbaar zijn bij het inspecten van een object of in de script debugger van Firebug en (b) ze zijn niet of via een omweg te unit-testen.
Met "In alle gevallen gaat het gebruik van private members verloren", doelde ik dan ook op de abstractiemethoden. Vandaar ook mijn voorkeur naar het gebruik van puur prototyped inheritance, zodat ik niets aan functionaliteit hoef in te leveren.
Geef eens een voorbeeld? Dat zou namelijk het hele OO-idee onderuit halen.
Sommige van deze implementaties werken door simpelweg eigenschappen te kopiëren naar ervende objecten. Dat maakt misschien wel dat het object overeenkomt met dat waarvan je "erft", maar de objecten hebben verder geen enkele relatie.
Omdat het je code overzichtelijker, compacter en beter onderhoudbaar kan maken. Het kan ook precies het tegenovergestelde doen. Het is maar net hoe je het toepast. Het valt of staat eigenlijk bij je object-model. Ikzelf gebruik eigenlijk alleen maar de bovengenoemde prototypal inheritance en dan zelden meer dan 2 nivo's diep.
Ik heb de behoefte om het duidelijker te maken nooit zo begrepen. Ik bouw zelf m'n objecten (en inheritance) als volgt op:

JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
 * Animal base object
 */
function Animal()
{
  // Private
  var self = this;

  // Public
  this.eat = eat;

  /**
   * Eat something
   * @param {Object} food An instance of Food.
   */
  function eat(food)
  {
    // Munch munch
  }
}

/**
 * Your favorite farm animal
 * @inherits Animal
 */
function Cow()
{
  // Call the prototype's constructor
  Animal.call(this);

  // Private
  var self = this;

  // Public
  this.moo = moo;

  /**
   * Moo?
   */
  function moo()
  {
    // Moo!
  }
}

// Inheritance
Cow.prototype = new Animal();

Tenzij ik wat over het hoofd zie, is dit vrij helder en is iedere ander aanpak, precies dat. Anders, maar niet per definitie beter.
Ik snap daarom dus ook niet waarom er zo'n focus is in de JS-wereld op die inheritance technieken. Naar mijn mening zijn er veel belangrijkere patterns die veel meer winst opleveren met minder verwarring (zoals bv het publisher/subscriber pattern).
Helemaal mee eens.
crisp schreef op donderdag 12 juni 2008 @ 23:34:
[...]

En daar zou je dan ook de conclusie uit kunnen trekken dat er legio mensen zijn die dan ook liever zien dat het OO-model van javascript meer lijkt op datgene dat ze gewend zijn van andere talen...
Dan zullen ze op ECMAScript 4 moeten wachten. Wrappers rond fundamentele eigenschappen van een taal vind ik het hoe goed bedoeld ook, altijd een verkeerde aanpak.

Men zou je ook raar aankijken als je een Visual Basic achtige wrapper rond C++ gaat schrijven, puur omdat je niet bekend bent met C++. Waarom moeten we JavaScript dan ineens op Ruby of Java laten lijken. Of nog erger, een Toolkit bouwen waarin je in Java kan programmeren, waarna het vertaald wordt naar JavaScript.
Object.extend() is overigens geen abstratie-method maar gewoon een helper method om in 1 keer meerdere prototyped methods toe te kennen (met eventueel een check of een dergelijke method al bestaat)
Nadeel ervan is dat je ook daar je private members verliest, dat is eigenlijk de reden dat ik 'm erbij noemde.

Verwijderd

je doet wel een beetje raar met je members. In animal is eat een instance member, terwijl in cow het een prototype member is.

ergo als je doet
var bugBlatterBeastOfTraal = new Animal()

en vervolgens
Animal.eat = function(){/* braak */};
dan heeft bugBlatterBeastOfTraal nog de originele eat functie, niet de nieuwe van Animal

Bij cow linkt dat wel door

je maakt nou juist geen gebruik van de kracht van de prototype member, alleen voor "inheritance" dus.

[ Voor 23% gewijzigd door Verwijderd op 14-06-2008 12:44 ]


  • Victor
  • Registratie: November 2003
  • Niet online
Verwijderd schreef op zaterdag 14 juni 2008 @ 12:37:
je doet wel een beetje raar met je members. In animal is eat een instance member, terwijl in cow het een prototype member is.

ergo als je doet
var bugBlatterBeastOfTraal = new Animal()

en vervolgens
Animal.eat = function(){/* braak */};
dan heeft bugBlatterBeastOfTraal nog de originele eat functie, niet de nieuwe van Animal

Bij cow linkt dat wel door

je maakt nou juist geen gebruik van de kracht van de prototype member, alleen voor "inheritance" dus.
Correct me if 'm wrong, maar prototype members hebben geen toegang tot private members. Vandaar deze aanpak.

Verwijderd

Verwijderd schreef op zaterdag 14 juni 2008 @ 12:37:
ergo als je doet
var bugBlatterBeastOfTraal = new Animal()

en vervolgens
Animal.eat = function(){/* braak */};
dan heeft bugBlatterBeastOfTraal nog de originele eat functie, niet de nieuwe van Animal

Bij cow linkt dat wel door

je maakt nou juist geen gebruik van de kracht van de prototype member, alleen voor "inheritance" dus.
At runtime methods aanpassen voor een hele class zodat alle instances de nieuwe method krijgen heb ik echt nog nooit gedaan. Krachtig? Ja. Handig? Niet echt.
Victor schreef op zaterdag 14 juni 2008 @ 11:15:
JavaScript:
1
2
3
4
5
6
function Animal()
{
  var self = this;
  this.eat = eat;
  function eat(food) { };
}
Wat je hier doet met eat() doe ik persoonlijk liever niet omdat je dan de mogelijkheid verliest om this te gebruiken in je methods. Dan moet je weer met var self = this aan de slag. Als een method public is dan definieer ik 'm het liefst gewoon meteen als this.eat = function() { }. Dan zie je ook meteen aan de method declaratie dat 'ie public is, nu moet je dat ergens anders opzoeken (this.eat = eat).

  • Victor
  • Registratie: November 2003
  • Niet online
Verwijderd schreef op zaterdag 14 juni 2008 @ 15:27:
Wat je hier doet met eat() doe ik persoonlijk liever niet omdat je dan de mogelijkheid verliest om this te gebruiken in je methods. Dan moet je weer met var self = this aan de slag. Als een method public is dan definieer ik 'm het liefst gewoon meteen als this.eat = function() { }. Dan zie je ook meteen aan de method declaratie dat 'ie public is, nu moet je dat ergens anders opzoeken (this.eat = eat).
Het niet kunnen gebruiken van this binnen de functie gaat alleen op als ik de functie binnen de constructor functie aanroep. Als ik 'm als method van een instantie aanroep, wijst this wel naar het juiste object (de instantie).

Verder ben ik niet zo'n fan van het definiëren van functies in de vorm van expressies, omdat dit invloed heeft op wanneer ik m'n functie kan aanroepen. Aangezien ik de daadwerkelijke constructor code vaak hoog in de constructor functie plaats (en daarbij nog wel eens lokale functies aanroep), zou dat betekenen dat ik die code moet gaan verplaatsen omdat deze functies nog niet gedefinieerd zijn. Ik kies daarom liever voor declaratie van functies.

This zal ik ook niet zo vaak nodig hebben overigens, omdat al m'n functies gedeclareerd worden in de scope van de constructor functie en dus direct zijn aan te roepen. Dit geldt tevens voor variabelen; Deze vallen ook binnen de lokale scope en zijn direct te gebruiken. Eigenlijk heb ik this dus alleen nodig voor members van het object waar ik van erf... Daar kan ik dan wel mee leven ;)

Als laatste vind ik het persoonlijk juist weer wel handig om op één plek te definiëren welke functies public zijn, maar dat is persoonlijke voorkeur. Ik plaats overigens in het commentaar van een functie wel vaak @method als 'ie public is, om het ook daar duidelijk te maken.

var self = this; gebruik ik overigens voornamelijk voor functies die als event handler dienen, omdat daarbij de this referentie al snel onduidelijk wordt.

Verwijderd

public/private is zoo klassiek oo denken. Dat heb je in js gewoon niet. Je hebt alleen in en uit scope members.

Voor een complete "klasse" (prototype chain dus) een method vervangen doe ik wel eens in het geval van state changes. Als je systeem in een andere state staat, krijgt de hele chain (of een gedeelte ervan) een gewijzigde functionaliteit. Vind ik persoonlijk weer netter dan een switch in elke method.

Iig, de prototype chain is krachtig, maak daar gebruik van, niet noodzakelijk alleen voor "inheritance" (want dat bestaat ook niet in javascript)

  • Victor
  • Registratie: November 2003
  • Niet online
Verwijderd schreef op zondag 15 juni 2008 @ 11:50:
public/private is zoo klassiek oo denken. Dat heb je in js gewoon niet. Je hebt alleen in en uit scope members.
Noem het hoe je wil, komt op hetzelfde neer. Ik wil functies en variabelen tot een bepaalde scope kunnen beperken en dat kan op die manier niet.
Voor een complete "klasse" (prototype chain dus) een method vervangen doe ik wel eens in het geval van state changes. Als je systeem in een andere state staat, krijgt de hele chain (of een gedeelte ervan) een gewijzigde functionaliteit. Vind ik persoonlijk weer netter dan een switch in elke method.

Iig, de prototype chain is krachtig, maak daar gebruik van, niet noodzakelijk alleen voor "inheritance" (want dat bestaat ook niet in javascript)
Nou, daar botsen we dan al, want ik zou nooit, al biedt de taal de mogelijkheid aan, even de werking van een method veranderen, om wat voor reden dan ook.

Maar goed, ik schrijf mijn code in JS dan ook strong typed; D.w.z. ik controleer van ieder argument het datatype en/of het wel een instantie is van het juiste object. Verder gebruik ik alleen getter en setter functies, om zo het risico op loose typing nog verder terug te dringen.

Verwijderd

Mja, botsen, ik ben het echt niet oneens met je opzet hoor. Het is duidelijk, je doet oo, dat is echt veel en veel beter als wat ik in het dagelijks leven tegenkom.

En of je nou methods wil hertoewijzen (ik vervang de methods niet, die blijven in de private scope, maar ik vervang method die aan een variabele hangt door een andere, tijdelijk) of niet, ik vind je manier wat inconsistent (namelijk: eat() is in Animal een instance member, terwijl dat het in Cow niet is).

Je zou het ook vanuit memory management kunnen bekijken als je wilt: elke instantie van Animal, levert je een nieuwe instantie van je eat() method. In elke instantie van Cow blijft eat() verwijzen naar de method van die ene instantie van Animal die aan z'n prototype hangt. Dat is een verschil, en dus inconsistent. Wat je daar van vindt is weer een tweede. Ik kan nou ook weer niet zeggen dat de wereld er vanaf hangt.

Wat ik zelf vaak (eigenlijk altijd) doe is gebruik maken van een functie scope om de boel te creeren, zo heb je de private member maar 1 keer gedefinieerd (en niet in elke instantie):

JavaScript:
1
2
3
4
5
6
7
8
9
10
var MyClass = function() {
  function private() {
    this.foo = "bar";
  }

  function MyClass() {
    private.call(this);
  }
  return MyClass;
}();


En gooi ik de hele boel in "packages" (een object)
JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
13
var myPackage = function() {
  var _package = myPackage||{};

  function privateMethod() {}

  function moo() {
    privateMethod();
  }

  _package.foo = "bar";
  _package.moo = moo;
  return _package;
}();

[ Voor 26% gewijzigd door Verwijderd op 16-06-2008 09:20 ]


  • Clay
  • Registratie: Oktober 1999
  • Laatst online: 14-11 16:23

Clay

cookie erbij?

@Victor, ik denk dat de issues die jij met JS hebt deels gewoon uit je aanpak voortkomen. Je kan JS zich behoorlijk als een andere taal laten gedragen, en er dingen inhacken die overeenkomen met concepten uit "hogere" talen, maar ergens houdt het gewoon op. Als ik jou was zou ik gewoon accepteren dat JS anders in elkaar zit, en de kracht ervan omarmen ipv halstarrig te proberen de werking te veranderen. ;)

Een bijkomend probleem van:
JavaScript:
1
2
// Inheritance
Cow.prototype = new Animal();


is overigens dat instanties van Cow denken dat ze instanties van Animal zijn; de .constructor van Cow instanties gaat daar op deze manier namelijk naar verwijzen. Mocht je Super constructor html genereren krijg je zo bovendien ook een probleem, dan zou je daar weer iets voor moeten gaan verzinnen.

Als je gewoon prototype functies overerft met een helper method hoef je die super helemaal niet als nieuwe instantie aan te roepen, de call (of apply) die je in de constructor doet zorgt er immers ook wel voor dat alles wat in de Super aan de instantie gehangen wordt er in de nieuwe klasse instantie ook aan komt.

Instagram | Flickr | "Let my music become battle cries" - Frédéric Chopin

Pagina: 1