Objektumorientált programozás JavaScriptben
A következőekben szeretném folytatni a JavaScript nyelv bemutatását, amit a függvényekkel kezdtem el.
A további cikkekben szeretnék jobban elmélyedni olyan témákban, mint a
- funkcionális programozás,
- eseménykezelés,
- objektumok,
- valamint kitérni arra, hogyan változnak meg a fentiek az ECMAScript (ES) következő változataiban.
Ezekhez korszerű, ES5 kompatibilis JavaScript motort feltételezek, de kitérek rá, hogyan lehet megvalósítani mindezt régebbi böngészőkben is (amennyiben egyáltalán lehetséges).
A JavaScript egy objektumorientált (OOP) programozási nyelv. Egy prototípusos nyelv, ami azt jelenti, hogy az öröklődést prototípusok segítségével, és nem osztályokkal valósítja meg. A JavaScriptben minden objektum, vagy használható objektumként.
Primitívek
A JavaScriptben öt primitív típus van (boolean
, number
, string
, null
és undefined
), és az utolsó kettő kivételével automatikusan objektummá alakulnak, amikor meghívjuk egy metódusukat, majd automatikusan vissza is alakulnak primitívvé.
false.toString() === 'false' // true
'string'.slice(2).replace('r', 'k') === 'king'; // true
typeof 1 === 'number'; // true
A primitív érték alakíthatatlan (immutable), ugyanakkor becsomagolhatjuk egy objektumba a csomagoló függvényével: a Boolean(), Number() vagy String() függvényt a new
operátorral meghívva (vagy az Object() függvényt használva), és átadva neki az értéket:
new Boolean(true) === true; // false
new Boolean(true).valueOf() === true; // true
typeof new Number(1) === 'object' // true
Object('string') instanceof String // true
A becsomagolt primitív mostmár egy teljes értékű objektum, kicsomagolni a valueOf() metódus meghívásával tudjuk. Mivel azonban nincs olyan eset, ahol az explicit csomagolás indokolt volna, használjuk ki, hogy a primitív értéket a futtatókörnyezet automatikusan be- és kicsomagolja.
Objektumok
Ahogy korábban írtam, a JavaScriptben, hasonlóan a Javához, minden objektum (a primitívek kivételével). Ugyanakkor a Javától eltérően itt minden objektum egy kulcs–érték pár tároló. Objektumokat nagyon egyszerűen tudunk létrehozni: az objektum konstruktor segítségével, illetve objektum literál használatával (ez utóbbi az ajánlott):
var object1 = new Object();
var object2 = {};
A Javával és sok más nyelvvel ellentétben további tulajdonságokat adhatunk egy objektumhoz a létrehozás után is, a pont szintaxissal, illetve szögletes zárójel használatával:
var object3 = {};
object3.poperty1 = 'value1';
object3['property2'] = 'value2';
A tulajdonságok nevei sztringek (ES6-ban [ES 2015-ben] kiegészülve a Symbol típussal), és akármilyen értéket tárolhatnak: primitívet és objektumot is. Az értékeket már az objektum literál felírásakor is megadhatjuk:
var object4 = {
property3: 1,
'continue': function () {
return null;
}
};
A JavaScriptben egy függvényt metódusnak nevezünk, ha az egy objektum tulajdonsága. Mivel JavaScriptben minden függvény egyben objektum is, így természetesen megadható tulajdonságnak, így lesznek az objektumoknak metódusai.
Prototípus lánc
Minden objektumnak van egy prototípusa ([[Prototype]]
), ami közvetlenül nem hozzáférhető (habár néhány futtatómotor elérhetővé teszi a __proto__ tulajdonságon keresztül). Ha az Object()
konstruktort vagy az objektum literált használjuk objektumunk létrehozására, akkor [[Prototype]]
tulajdonsága az Object
prototype
objektumára fog mutatani (és ezzel megörököl néhány tulajdonságot tőle, mint amilyen a toString() és a valueOf()
).
Amikor egy objektum egy tulajdonságát akarjuk elérni, akkor a futtatómotor előbb az objektum saját tulajdonságai között keresi, majd ha itt nem találja, akkor az objektum [[Prototype]]
értékét lekérdezve feljebb lép a prototípus lánc mentén, és itt fogja keresni a tulajdonságot, egészen addig, amíg el nem éri a null
-t a prototípus lánc végén.
+------------------+ | null |<--+ +------------------+ | | +------------------+ | +-->| Object.prototype | | | +------------------+ | | | [[Prototype]] |---+ | +------------------+ | | +------------------+ | | String.prototype |<--+ | +------------------+ | +---| [[Prototype]] | | +------------------+ | | +------------------+ | | new String('') | | +------------------+ | | [[Prototype]] |---+ +------------------+
Öröklődés
JavaScriptben az öröklődést hagyományosan a prototípus lánc kiterjesztésével oldjuk meg. Új objektum létrehozásakor az előbb leírt prototípus lánc végéhez adunk egy új elemet. Például a var object5 = {};
a következő prototípus láncot eredményezi:
+------------------+ | null |<--+ +------------------+ | | +------------------+ | +-->| Object.prototype | | | +------------------+ | | | [[Prototype]] |---+ | +------------------+ | | +------------------+ | | object5 | | +------------------+ +---| [[Prototype]] | +------------------+
Felülírhatjuk az örökölt tulajdonságokat, vagy hozzáadhatunk újakat, magában az objektumban, illetve bárhol a prototípus láncban. A prototípus láncban való felülírás azt jelenti, hogy minden objektum, aminek a prototípus lánca tartalmazza a felülírt objektumot, szintén megkapja az új tulajdonságot. Ez a működés nagy rugalmasságot ad a nyelvnek, ugyanakkor rengeteg hiba okozója is lehet.
Object.create()
Az Object.create() segítségével könnyen megvalósíthatjuk az öröklődést (mellékhatások nélkül):
var Prototype = {
name: 'Prototype',
sayName: function () {
console.log('My name is: ' + this.name);
}
};
Prototype.sayName(); // > My name is: Prototype
var object6 = Object.create(Prototype);
object6.name = 'object6';
object6.sayName(); // > My name is: object6
Habár a kódunkban nem deklaráltuk az object6.sayName()
metódust, meghívásakor a motor kikeresi a Prototype
objektumban megtalálhatót a prototípus láncban lépkedve.
A megmaradt problémánk, hogy a name
tulajdonságot külön sorban tudjuk csak felülírni. Ezt megoldhatjuk úgy, hogy átadunk egy propertiesObject objektumot az Object.create()
-nek:
var object7 = Object.create(Prototype, {
name: {
value: 'object7',
enumerable: true,
writable: true,
configurable: true,
}
});
object7.sayName() // > My name is: object7
Azokban az implementációkban, amik nem valósítotják meg az Object.create()
függvényt, egy polyfill használatával könnyen megadhatjuk az alapvető működést (a propertiesObject
használatának kivételével):
if (typeof Object.create !== 'function') {
Object.create = (function () {
var Heir = function () {};
return function (prototype) {
if (arguments.length > 1) {
throw Error('Second argument not supported');
}
if (typeof prototype !== 'object') {
throw TypeError('Argument must be an object');
}
Heir.prototype = prototype;
return new Heir();
};
})();
}
Az Object.create()
használatával még mindig ránk marad az objektumok inicializálása. Itt jönnek segítségünkre a konstruktorok.
Konstruktorok
A konstruktorok szokásos függvények. Azonban amikor a new
operátorral hívjuk meg őket, akkor konstruktorként viselkednek, és visszaadnak egy objektumot (még akkor is, ha primitívet vagy semmilyen értéket nem adnának vissza a return
utasítással).
A függvényeket, amiket konstruktorként szeretnénk használni, olyanra érdemes tervezni, hogy úgy nézzenek ki, és úgy is viselkedjenek, mint egy konstruktor. Ezért legjobb, ha nagybetűvel kezdjük a nevét, ezzel jelezve a használójának, mi a fejlesztő szándéka:
function Shape() {
console.log('Constructor called');
}
// A () jelek opcionálisak, ha egy függvényt konstruktorként hívunk meg.
var shape = new Shape(); // > Constructor called
console.log(typeof shape); // > object
Közvetlenül a konstruktorfüggvény meghívása előtt a JavaScript motor létrehoz egy objektumot, melynek [[Prototype]]
-ja a konstruktor prototype
tulajdonságára mutat. Ezen új objektumot ezután a függvény a this
értékeként kapja meg. Ha a függvény nem ad vissza semmit, vagy nem objektumot ad vissza, a this
értéke kerül automatikusan visszaadásra.
Kiterjeszthetjük az objektumunkat úgy, hogy újabb tulajdonságokat adunk a konstruktora prototype
objektumához, akár azután is, hogy az objektum létrejött:
Shape.prototype.move = function () {
console.log('Shape moved');
};
shape.move() // > Shape moved
Minden objektum, amit a konstruktorfügvénnyel hoztunk létre, ezután megörökli az összes tulajdonságot, amit a prototype
-hoz adunk, mivel azok bekerülnek a prototípus láncba.
Öröklődés konstruktorokkal
Kombinálhatjuk az öröklődést és konstruktorfüggvényeket. Ezzel olyan objektumokat nyerünk, melyek egymástól örökölnek tulajdonságokat. Ehhez újabb konstruktort hozunk létre, annak prototípusát megfeleltetjük a szülő konstruktorának prototype
-jából létrejött objektumnak, végül pedig helyreállítjuk a konstruktor prototípusának constructor
tulajdonságát:
function Rectangle() {}
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
Függvény deklarálásakor a JavaScript motor automatikusan hozzáadja a prototípushoz a függvényre mutató constructor
hivatkozást. Miután az objektum prototípusát felülírtuk, vissza kell állítanunk a constructor
értékét az eredetire.
Ezután már használhatjuk is az új konstruktort. Az általa generált objektumok ezután mind a szülő, mind pedig a konstruktor prototípusának tulajdonságait megöröklik:
var rectangle = new Rectangle();
console.log(rectangle instanceof Rectangle); // > true
console.log(rectangle instanceof Shape); // > true
rectangle.move(); // > Shape moved
A szülő függvényeinek meghívása
Az előző megvalósításnak van egy kisebb hibája. Nem hívja meg a szülő (superclass) konstruktorát, amikor egy új objektumot hoz létre. Ezt viszonylag könnyű orvosolni. Meg kell hívni a szülő konstruktorfüggvényének call()
metódusát, átadva neki az újonnan létrejött this
objektumot, valamint a további paramétereket, amennyiben szükséges:
function Rectangle() {
Shape.call(this);
}
Mivel a fenti esetben mindig emlékezni kell mi volt a szülőosztály konstruktora, ezért a megoldás nem túl elegáns, ezen kívül szorossá teszi a csatolást a szülő és a gyermek között, mivel a kódban többször kell, hogy szerepeljen a szülő konstruktorának a neve. Kicsit dinamikusabbá tudjuk tenni ezt a csatolást azzal, ha az öröklődést egy segédfügvénnyel valósítjuk meg. Pár függvénytár ad is nekünk ilyen kényelmi funkciót, mint amilyen a util.inherits()
Node.js-ben vagy a goog.inherits()
a Google Closure Library-ben. Segítségükkel az öröklődés és a szülő elérése átlátszóvá válik. Amennyiben nem használunk olyan függvénytárat, ami segít ebben, könnyen megvalósíthatjuk:
function inherits(childConstructor, superConstructor) {
childConstructor.super_ = superConstructor;
childConstructor.prototype = Object.create(superConstructor.prototype, {
constructor: {
value: childConstructor,
enumerable: false,
writable: true,
configurable: true
}
});
}
Ezek után már használhatjuk is, hogy ezzel hozzuk létre a prototípus láncunkat, és meghívjuk a konstruktort és prototípusának metódusait:
function Shape() {
console.log('Constructor called');
}
Shape.prototype.move = function () {
console.log('Shape moved');
};
function Rectangle() {
Rectangle.super_.call(this);
}
inherits(Rectangle, Shape);
Rectangle.prototype.move = function () {
console.log('Rectangle moved');
Rectangle.super_.prototype.move.call(this);
};
Ezek után, ha létrehozunk egy új Rectangle
objektumot, és meghívjuk a move()
metódusát, akkor az meg fogja hívni a szülő prototípusának move()
metódusát is. Mivel ezzel eltávolítottuk a közvetlen függést a szülőtől szinte minden helyen, könnyebbé vált lecserélni a szülőt a jövőben például egy Polygon
típusra, amennyiben ez szükséges, anélkül, hogy mindenhol át kellene írni a hivatkozást.
Remélem, sikerült felkeltenem az érdeklődést az objektumorientált JavaScript programozás témája iránt azokban is, akik nem igazán ismerték vagy használták korábban.
■
Köszi a cikket! Hasznos volt!
thx
Remélem lesz kedved és időd folytatni.
Köszönjük!
Köszönöm én is.
Kérdések
Öröklődés
Ha öröklődést használunk, a kódunk olyan lesz, mint egy ház: alap -> falak -> tető. A házak egyik alapkövetelménye, hogy stabilak legyenek, egyszer felépítik őket, aztán amíg le nem bontják, általában csak a felszínt csinosítgatják a tulajdonosok. Évtizedenként lefestik, húsz-harminc évenként a cserepeket is lecserélhetik, de az alapokhoz és a falakhoz nem nyúlnak, hisz akkor egyrészt összedőlne az egész, másrészt a köztes rétegek ezt nem nagyon engedik.Szoftvereknél ez nem így működik, a fejlesztés során sokminden változik, mert idő kell, amíg a programozók átlátják a megoldandó feladatot, így sok összefüggés csak munka közben derül ki, másrészt, ugye, evés közben jön meg az étvágy, a megrendelők előszeretettel térnek el az eredeti megállapodástól, és újabb funkciókat kérnek az egyre kevésbé lelkes gárdától.
Mit jelent ez a gyakorlatban? Ha például az alapobjektumon változtatni kell, akkor az egész kódot át kell nézni és át kell írni, mert az kihatással van minden leszármazottjára. Ugyanígy, ha a kommunikációs interface-ben kell átírni valamit (megváltozik az API, a paraméterek), akkor az ugyancsak sokmindent érint.
Ez így rendkívül rugalmatlan, és ebből következik, hogy öröklődést igazából csak "házon belül" érdemes használni, hiszen ha elkészítek egy rutinkönyvtárat ezzel a technológiával, kiteszem a netre, majd a későbbiekben valamit átírok benne, akkor mindenki módosíthatja a kódját, ami erre épül.
Ezekből következik a kérdés: Hol érdemes öröklődést használni?
Nagyobb projekteknél biztosan nem, hisz minden újabb objektumréteg csak ridegebbé teszi a kódot a későbbi módosításokkal szemben. Persze, ha a programozó látnoki képeségekkel rendelkezik, és tudja, hogy márpedig ezek az objektumok nem fognak változni, akkor meg lehet vele probálkozni, de erre általában annyi az esély, mint egy lottóötösre.
Akiket nem győznek meg a fentiek, érdemes elolvasni a következő blogbejegyzést Karl Lieberherr húsz éve megjelent tanulmányáról, ami Demeter törvényére alapulva tudományosan támasztja alá az öröklődés gyengeségeit.
Ha az öröklődés ennyi problémával jár, az ES6-ban miért vezették be a
class
-t és azextends
-et? Ami, ugye, nem más, mint szintaktikai sugár a prototípus alapú örökléshez. Mi volt ennek a gyakorlati oka? Politikai (hogy bevonzzák a külsős fejlesztőket) vagy racionális? Nagyon úgy látszik, hogy ez utóbbi érvet nyugodtan kilőhetjük.Egységbezárás
Ha az öröklődés kiesik, mi az értelme az objektumoknak? Ha nincs öröklődés, akkor egyobjektum.metodus()
hívásban az objektumnak nincs sok szerepe, gyakorlatilag csak névtérként használjuk. Akkor viszont miben több/jobb attól, mintha normál függvényeket hívnánk egy prefix-szel?A javascriptben a pár primitíven kívül minden objektum, minden függvény, tulajdonság valamilyen objektumhoz tartozik. Ez nagyon szép elképzelés, ha jól építették volna fel a beépített objektumok láncolatát, vagy pedig egy függvény több objektumhoz is tartozhatna. Erre mutat rá például a
function.call()
és afunction.apply()
, ami nyilvánvaló nyelvi bűz (language smell).Miért kell az adatokat és a függvényeket összeházasítani? Ha egy metódus csak a saját adatain tud műveleteket végezni, akkor ha úgy hozza a sors, hogy máshol is kéne használnom, akkor ki kell operálnom az eredeti objektumból, és átírni paraméterezhetőre. Emiatt a kód még rugalmatlanabb lesz – nem lenne egyszerűbb eleve külön kezelni az adatokat és a függvényeket?
Nyilvánosság
Javascript objektumokban csak publikus tulajdonságokat és metódusokat használhatunk – ez tovább erősíti a fenti hipotézist, miszerint az objektumok csak kvázi névtérként funkcionálnak. Ha nem tudjuk megvédeni azt, ami a mienk, akkor beszélhetünk-e OOP-ről? Természetesen felvetődhet az a kérdés, hogy van-e értelme megvédeni bármit is, hisz egy javascript kód nem fekete dobozként működik, mint mondjuk egy C rutinkönyvtár, hanem ott van nálam a teljes forráskód, amit úgy módosíthatok, ahogy csak tetszik.Ha valaki mégis úgy gondolja, hogy megvédené a belső változóit, akkor használhat zárlatokat (closure). Ekkor viszont a (prototípus alapú) öröklődés szinte teljesen kiesik, hisz egy utólag hozzáadott metódus nem látja a privát tagokat, azaz a szülőobjektum ismét csak egy névtérré degradálódik.
OOP
A fentiek után felmerülhet a kérdés, hogy van-e egyáltalán OOP a javascriptben? Az öröklődés csak rendkívül korlátozottan használható, semmi sem védi meg a belső változókat és függvényeket a külső módosítástól, az egységbezárás pedig rugalmatlanná teszi a kódunkat. Ha így van, akkor miért programozzunk így, és miért ne válasszuk inkább a jóval egyszerűbb függvényekkel való munkát?Sőt, ha továbbmegyünk, a fentiek alapján egyértelmű, hogy az OOP mint elmélet is instabil lábakon áll. Akkor pedig miért erőltetik?
Védelem
slice
nevű függvényt, ami azt csinálja, mint azArray.prototype.slice
és aString.prototype.slice
. Ekkor ha valaki felülírja, még csak véletlenül is ezt a globális függvényt, akkor az alkalmazásunk egyáltalán nem fog működni. Ugyan a fenti névterekkel is megoldható lenne, de a JavaScript nem támogat névtereket, egyedül objektumokkal tudjuk emulálni azt. És mivel a névterünk JavaScriptben egy objektum, máris elértünk az OOP-hoz. Azaz az objektumunknak tulajdonságai, és metódusai vannak, még akkor is, ha tulajdonképpen ez az objektum egy egyke (singleton
).Az ellen nem véd, deviszont...
Array.prototype.slice = function() { alert('hehe'); };
"Véletlen"
Életszerűtlen
sajat_objektum.slice()
nevű metódus semmivel sem védettebb, mint egysajat_fuggvenytaram_slice()
nevű függvény.Ha például az alapobjektumon
foo(adat, param1, param2)
ugyanúgy objektum-orientált mint azadat.foo(param1, param2)
.call
ésapply
oldja meg, hogy egy függvénynek te tudod beállítani a kontextusát... Amúgy ez mitől nyilvánvaló language smell?@api private
annotáció is elegendő a jelzésükre. Mellesleg Java vagy C++ esetén is ugyanúgy ki lehet bányászni a privát adattagokat kívülről.Köszönöm a válaszaidat, bár
banki példa
játékos példa
Jedi-s példa
Array.prototype.slice.call(arguments)
-et lehet látni, ami azt mutatja meg, hogy rosszul lett megtervezve azarguments
objektum, valamint azt, hogy a többszörös öröklődés nem támogatott. Ezekre miért nincs megoldás a mai napig?Ha valami nem világos, forduljunk bátran a wikipediához!
this.x = x;
this.y = y;
this.sugar = sugar;
}
Kor.prototype.rajzol = function kor_rajzol() {
this.x, this.y és this.sugar segítségével rajzolunk
}
var kor = new Kor(26);
Kor.rajzol();
Itt az adatok és a rajtuk műveleteket végző függvények egységbe vannak zárva. Node mi van, ha később egy ettől független projektben szükségem van egy olyan függvényre, ami visszaadja egy tömbben egy kör pontjait, és így mondjuk le tudom ellenőrizni, hogy egy adott pont rajta van-e a körön. Ekkor nem tudom egyszerűen kiemelni a metódust, mert bele van égetve, hogy a saját objektum változóit használja. Ennél jóval rugalmasabb, ha eredetileg egy független függvényként írom meg:
(...)
}
Analóg példa, hogy PHP-ban például a script elején végezzük el az adatok feldolgozását, összeállítjuk az adattömböt, és amikor végeztünk, akkor kezdjük el összeállítani a HTML-t. Az OOP pont ugyanolyan spagettikódot eredményez, mint a következő pszeudokód:
foreach ($adatok as $sor) {
?>
<td><?php print $sor['mezo1']; ?><br><?php print $sor['mezo2']; ?></td>
<?php
}
Karbantarthatóság, átláthatóság, modularitás
versus
fuggveny(adat, parameter);
Számomra nagyon egyformának tűnnek, nem látom az objektum egyértelmű előnyét.
Mire számítasz ettől a
Nyilván, ahogy fejlődik egy
Nem erőltetném a dolgot, ha
A körös példával azt illusztráltam, hogy procedurálisan, azaz szimpla függvények használatával sokkal természetesebb a Separation of concerns és a single responsibility.
Hm?
Több helyre
A te példádban hogyan oldanád
Ha
If: Megtöri a kód
Tömbök (ebben a példában): Feleslegesen allokált memória. Ha nagyon sebességkritikus a project, akkor azt is lehet mondani, hogy cache pollutiont okoz feleslegesen. Ezen felül nem nyújt contractot a két résztvevő között. Ez vagy baj, vagy nem. Itt szerintem jobb, ha elkerüljük.
Stringly TypedA riff on
A riff on strongly typed. Used to describe an implementation that needlessly relies on strings when programmer & refactor friendly options are available.
For example:
Method parameters that take strings when other more appropriate types should be used.
On the occasion that a string is required in a method call (e.g. network service), the string is then passed and used throughout the rest of the call graph without first converting it to a more suitable internal representation (e.g. parse it and create an enum, then you have strong typing throughout the rest of your codebase).
Message passing without using typed messages etc.
Excessively stringly typed code is usually a pain to understand and detonates at runtime with errors that the compiler would normally find.
Egyébként ez a sok elágazás, amivel bohóckodsz, pont a dynamic dispatch, amit amúgy ingyen (és automatikusan) megkapnál ;-)
Dynamic dispatch
Node ehhez az elején valahogy inicializálni is kell:
Ezek általában nem a fő
Az a baj, hogy nagyon
A lényeg, hogy ha egy A objektum megkap egy B contractú implementációt, akkor A-nak nem kell tudjon arról, hogy az az adott B implementáció hogyan viselkedik, ő csak egyszerűen meghívja. Gondolj a pont renderelős példára. Mindenféle if nélkül megoldható, hogy fileba, vagy canvasra rendereljen az alakzat. A feltétel a megfelelő absztrakciós szinten kerül implementálásra, ami semmiképp nem az alakzat.
Inplementáció
Tegyük fel, hogy az ügyfél újabb kéréssel érkezik:
Természetesen, hisz
+1
+1
Jelen esetben a Factory
Nekem egy mentorom azt mondta, amikor azt nézed, két megoldás közül melyiknél kell kevesebbet gépelni, baromira nem érted, miről szól az egész.
Ha van autocomplete, akkor
Sajnos nem igazán jó a
Ezt szerintem te sem gondolod
Élő példa
Ahol a leszármazott
Kicsit refaktoráltam az oo
Amit továbbra sem fogsz fel, hogy az OO lényege az absztrakciós szintekre bontás, tehát ha megnézek egy absztrakciós szintet, akkor látom a metódus és változónevekből, hogy mit csinál, ha érdekel, hogy hogyan csinálja, akkor megnézem az alatta levő szintből a releváns részt, és így tovább. Szóval jelen esetben ha érdekel, hogy mit csinál a program, akkor elolvasom ezt:
A struktúrált módszernél a függőségek beinjektálását nem lehet annyira hatékonyan megoldani. Vagy csinálsz egy nagyon hosszú paraméter listát, vagy hatalmas adatstruktúrákat adsz át, vagy globális változókba teszel dolgokat. Mindegyik megnehezíti a tesztelést és átláthatóságot az OO-hoz képest, nekem legalábbis ez a tapasztalatom, de eddig csak nagyon rossz minőségű struktúrált kóddal dolgoztam, és ebből a szempontból a tiéd sem a legjobb. Valami ilyesmi asszociatív tömbös megoldás sokat dobna rajta átláthatóság szempontjából:
Köszönöm a választ, te
Pontosan ez a probléma, hogy
Legfeljebb két-háromszintes
DIC
Mi a különbség egy Dependency Injection Container és a globális névtér között az elnevezésükön kívül? Mi a különbség a singleton és a DIC között?
A globális névtérhez bármely
A singleton azt biztosítja, hogy egy scope-on belül (php script futása, classloader, DIC stb) egy osztálynak mindenki ugyanazon példányát éri el. Ennek minden előnyével és hátrányával. Egy DIC-ben nem csak singleton scope-ú objektumok létezhetnek.
Kontroll
az adatrejtés az pusztán csak
Azt elfelejtetted hozzátenni, hogy javascriptben, mert ők azt állítják.
Egyrészt javascriptes témában
Egyrészt PHP-ban van védelem,
Hm?
PPP. Persze reflection-nel
miközben ők sosem állítottak ilyet általánosságban, csakis a js-el kapcsolatban, mert annál nincs beépített ellenőrzés ppp-re. Bár foglalt szavak, a nyelv nem használja őket. JsDoc-ban is jelen vannak, szóval ha abban adjuk meg, hogy mi private, akkor szolgálhat dokumentációs célokat.
A jobb IDE-k (legalábbis webstorm) érti valamilyen szinten a jsdoc-ot, és figyelmeztet, ha private-nak megjelölt metódusokat vagy példányváltozókat akarsz elérni a globális névtérből. Szóval ami ppp-nél számít, hogy jelez a rendszer, hogy nem public interface-en keresztül akarsz hozzáférni egy-egy objektumhoz, ami általában rossz dolgokhoz vezet. Az, hogy vállalod e ennek a következményeit, csak rajtad múlik. Nagyjából ennyiről szól az adatrejtés. Amúgy nyelv függő, hogy milyen szigorúak a szabályok, js-ben azt hiszem a lehető leglazábbak, php-ban reflection-nel lehet csak kikerülni őket, más nyelveken lehetnek akár még szigorúbbak.
Ha biztonsági célokat
Az ellen nem véd, deviszont...
A "nem lehet", az arról
Szerintem senki sem gondolta a fenti értelemben a "nem lehet"-re.
A DIC meg annyival azért több egy sima globális változónál, hogy akármit azért nem lehet az adott változókba beletenni, de hogy pontosan ez mennyire kötött, az az egyes DIC megvalósításoktól függ. (mert lehet olyat is implementálni, ami aztán egyáltalán semmiben nem jobb mint a globális névtér, és akkor igazad lenne, de úgy nagyáltalánosságban nincs igazad, mert mint írtam a megvalósítástól, és nem az elmélettől függ. Elméletileg jobb, gyakorlatilag lehet ugyan olyan vacak. Tehát rossz példát mindig fogsz majd tudni mutatni, de akik használtak már normális DIC, azokat nem fogod meggyőzni)
pp
Függőség
Ennyi erővel félre lehetne tenni a globálisoktól való mániákus félelmet, megegyezni a kollegákkal, hogy azokat fejvesztés terhe mellett csak függvényeken keresztül módosítjuk, és máris megszabadultunk egy függőségtől.
Persze, hogy meg lehet ezt
pp
Melyik nyelvi elemekre
Szerinted? pp
pp
Nem értem
Nem értem, "fent"-i alatt mit
Én arra gondolok, hogy a "Globális változók és megállapodás a kollégák között" módszer megoldására jó az OOP is. Tehát választhatsz. Kreálsz egy szóbeli megállapodást, vagy használsz egy nyelvben már meglévő megoldást.
Én mindkét megoldást el tudom fogadni, hogy melyik a jó, azt eldönteni most nem szeretném, mivel a feladattól, csapattól, stb.-től is függ. Szóval nem ezt vitatom, hanem azt, hogy kontextus nélkül dönteni kéne/lehetne.
Ennyi.
Uncsi már ez
Amikor új munkatárs érkezik, nem kell átnyálazni egy 200 oldalas kézikönyvet, hogy ha oda van írva kommentbe, hogy "privát változó", akkor azt ne módosítsa, meg fejvesztés terhe mellett ilyen meg ilyen függvényekkel kell globális változókat beállítani, de közben nagyon kell figyelni erre meg erre, ésatöbbi. Ért az OOP-hez, tudja mi az a private, meg mi az a DIC, aztán csókolom. Következő munkahelyen ugyanez (persze ha az ember megválogatja, hová megy), ha beszállsz open source projektekbe, ugyanez. Megvan a közös tudás, a közös nyelv és nem függünk arbitráris kitalációktól.
+1. Szerintem struktúrált
Gábor eddig nem említett
Szerintem jobb agyalni egy
Ha belegondolsz kb ennyi oo osztályokat adatstruktúrákra és függvényekre átfordítani:
oo
Ha nem alkalmazzuk a dependency inversion-t, akkor nyilván szorosan csatolt lesz a kód, ami módosíthatóság szempontjából zűrös, mert a magasabb absztrakciós szintekről begyűrűzhet kód az alacsonyabb absztrakciós szintekre. Pl a már említett if-else-es példányosítás esetében az if-else ágak és a feltétel ismétlődően megjelenik a builder-ben, ami nyilvánvalóan nem DRY. Ha viszont nincs ilyen probléma a kóddal, akkor csökkenthető a szoros csatolással a paraméterek száma, és így egyszerűsíthető a kód. Nyilván lehet haxolni global namespace-es változókkal is, ami szintén ugyanezt tudja csak teljesen követhetetlen módon.
struktúrált
Jaja, ezért mondom, hogy
Azt mondják, hogyha valamit
Nem cél, hogy a programozótól
Ha ez segít, akkor a barátod barátnője is olyan, mint egy privát változó. Semmi nem gátol meg benne, hogy fizikailag hozzáférj mocskos eszközökkel. De ezzel szabály ellen vétesz és komoly következményei lehetnek, ezért nem csináljuk. Ennyi.
Nem erőltetném a dolgot, ha
Az a baj, hogy az érdemi érveket figyelmen kívül hagyod. A neten pedig arról is rengeteg írást lehet találni, hogy a Föld ötezer éves.
A neten pedig arról is
Nice :).
Látod, ezért kérdeztem, hogy
Az öröklődés szintaktikai édesítőszer a kompozícióval és delegálással megvalósított implementálás felett. Akkor érdemes használni, ha egy meglévő adattípushoz új műveleteket akarsz hozzáadni, és ilyen esetben nem is okoz gondot.
Amikor nem akarjuk a magasszintű algoritmusainkat konkrét adattípushoz kötni, mert így módosítás nélkül használhatóak a későbbiekben bármilyen – az interfésznek megfelelő – adaton.
Az objektumorientáltság lényege a hagyományos procedurális programozással szemben az, hogy adatszerkezetekbe lokalizálja az állapotot, ahelyett, hogy globális változókban tárolná. C-ben, egyszerű függvényekkel is lehet objektumorientált kódot írni.
Akkor lehet, hogy nem érted a nyelvet:
Ami az
arguments
-et illeti, rosszul tervezett API-k vannak, de hát ezt pont olyannak mondjam, aki PHP-val dolgozik? Ami meg a többszörös öröklődést, az úgy volt, hogy valami vaskalapos hülye kitalálta, hogy az márpedig rossz, mert lehet rosszul is használni, és azóta tabu. Várj, keresek egy példát… mint az öröklődés!Ezt kohéziónak hívják. Ez a másik fele a loose coupling mantrának, aminek nélküle nincs sok értelme.
A függvényed lokális változóiba is bele lehet nyúlni futás közben, csak nem érdemes. Egyetértünk abban, hogy jobb, ha egy nyelv nem engedi?
Az öröklődés szintaktikai
Példa: a bankban bankszámlán tartjuk a pénzünket. Pár évtizede a bankban tudtunk kivenni pénzt, majd feltalálták a bankkártyát, így jóval több lehetőségünk volt hozzájutni ahhoz, ami a mienk.
bankszamla.prototype.bankkartya = ...;
Aztán kitalálták, hogy legyen értékpapírszámla, amin nem közvetlenül pénz van, hanem az általam vásárolt értékpapírok. Ezeket közvetlenül felvenni nem lehet, tehát ehhez a számlához nem tartozhat bankkártya. Ekkor írhatom át a bankszámla prototípusát, és a bankkártyát a gyerekekhez hozzá kell adnom egyesével.
Tehát amit leírsz ("Akkor érdemes használni, ha egy meglévő adattípushoz új műveleteket akarsz hozzáadni") igaz, de korlátok között. Ezen korlátok definícióját szeretném megkapni.
Akkor lehet, hogy nem érted a nyelvet:
alert(this);
}
var a = {};
var b = {};
a.f = f;
b.f = f;
A fenti példádban az
a.f
-ben azf
csak egy mutató az eredetif
függvényre, ami awindow
-ban csücsül. Ha meg szeretnéd azf
-et változtatni, akkor ehhez az egyhez kell visszanyúlnod, mert ha aza.f
-et írod át, akkor csak aza
metódusa lesz más.Tehát akkor én nem ismerem a nyelvet, vagy pedig te érveltél átgondolatlanul?
Ezt kohéziónak hívják. Ez a másik fele a loose coupling mantrának, aminek nélküle nincs sok értelme.
Példa: a bankban bankszámlán
Az első pár kérdésedre
versus
fuggveny(adat, parameter);
Számomra nagyon egyformának tűnnek, nem látom az objektum egyértelmű előnyét.
Megint dynamic dispatch. Ami nyilván szimulálható mindenféle if-es bohóckodással, csak semmi értelme.
Refaktoring könyv példája
Szerintem téves az a hozzáállás, hogy Móricka példákon bebizonyítjuk, hogy az öröklődés nem hatékony, majd ebből arra következtetünk, hogy akkor a nagy projekteknél nemhogy nem hatékony, de egyenesen káros. Nincs ilyen ok és okozat. Érdemes lenne végignézned egy olyan példát, amely egy-egy ilyen kommentbe már nem fér bele! Konkrétan ajánlom Martin Fowler Refactoring könyv első fejezetét. Egy nagy massza kódból szép lassan eljutunk egy kifejezetten kellemes kódig. Ha megnézzük a kód első verzióját, majd az utolsó verzióját, akkor szerintem a választás egyértelmű. Ha számodra nem, akkor arról lenne értelme beszélgetni.
Túl egyszerű?
Nem olvastam...
Én régebbi extjs-ben láttam 6
mixin
Ellenben a Refactoring-ban hozott példa pontosan ki lett találva, ott valóban minden előnyét élvezhetjük az öröklésnek. (videotékában a különböző besorolású filmeknél más-más a kedvezménnyel lehet kikölcsönözni, a folyamat végén lesz Movie osztály és lesz még annak néhány leszármazottja, és a kód pont olyan, amilyennek lennie kell)
Így arra gondolnék, hogy arra a kérdésre, hogy mikor érdemes használni öröklődést arra egy kiváló válasz a könyv példája. Ebben az esetben valóban kell használni.
A négyes verzióban bevezették
Jaja, ez még az extjs3 volt. Azt mondják a 4 tényleg sokkal jobb meg gyorsabb lett.
Ha a könyvről van szó, én már nem emlékszem a konkrét példákra. Arra emlékszem, hogy alapmű, ha valaki oop-t akar tanulni. Nagyon sok dolgot meg lehet érteni belőle a code smell-ről meg az absztrakciós szintekről. Ha még elolvassa az ember mellé a Clean Code-ot is, akkor elég jól képben lesz. Szerintem.
Szerintem a composition jobb
Miért? Ha valóban is-a a viszony a két osztály között, akkor csak többet gépelsz vele.
Egyszerűen az volt a
Ha van kedved megkeresni a
Ezzel együtt szívesen látnék olyan nyelvi eszközöket, amik nem kényszerítenek választásra az „altípus vagyok és öröklök mindent” és a „példányosítok egyet rejtve, és delegálom az összes hívást, amit nem akarok felülírni” között, mint például a C++ nem nyilvános öröklése vagy a Groovy
@delegate
annotációi.Innen le lehet rántani
Amivel én szórakoztam egy kis ideig, hogy egy drag&drop DOM tree editort rakjak össze a TreePanel és a TreeNode használatával, de feladtam. Akkor még nem volt elég kiforrott sem a tree, sem a drag&drop a rendszerben. (Lehet, hogy nem is ilyen megközelítéssel kellett volna nekiállni, hanem wysiwyg alapon, de ez most részletkérdés.)
Ezek az osztályok:
Ha például az alapobjektumon
Ahogy Bence is írta, ez minden interfészre igaz, és erre nem reagáltál érdemben. Ha a függvényeid argumentumlistája változik, akkor ugyanúgy el fog törni minden kód, ami rájuk támaszkodik.
A gyakran ismétlődő szintaktikai mintákat szokás beépített konstrukciókkal támogatni.
Az objektum polimorf argumentum.
Azért kell őket összeházasítani, hogy az adattípus-specifikus implementációt el lehessen rejteni egy interfész mögé, és utána mindenféle általános, az adattípusomat nem, csak a számára releváns interfészét ismerő algoritmusokat tudjak rajta alkalmazni. Tudod, mert így sokkal rugalmasabb és könnyebben bővíthető a rendszer.
Ez megintcsak igaz egyszerű függvényekre is: ha egy adattípus-specifikus algoritmus egy részét általánossá akarod tenni, akkor ki kell emeld.
Az objektumok adatstruktúrák. Az, hogy a JavaScript nem nyújt nyelvi eszközt annak dokumentálására, hogy mely adattagok részei az interfésznek és melyek nem, az egy fájó hiányosság, de hát azt sem mondhatom meg, hogy milyen típusúak a függvényeim paraméterei, szóval tulajdonképpen konzisztens a dokumentációra való ráutaltságom tekintetében.
Egyszer össze fogom számolni, hogy hányszor mondtuk már el, hogy a láthatóság nem biztonsági kérdés, hanem dokumentációs, és C++-ban is azt olvasok ki egy védett adattagból, amit akarok.
A privát adattagokat más nyelvekben sem látja a leszármazott osztály. És ez így is van jól, az öröklődés nem arra való, hogy megkerüld az interfészt, hanem hogy bővítsd.
Lássuk csak, vannak összetett adattípusok, van polimorfizmus: igen, van OOP a JavaScriptben.
Ahogy Bence is írta, ez
Ez megintcsak igaz egyszerű függvényekre is: ha egy adattípus-specifikus algoritmus egy részét általánossá akarod tenni, akkor ki kell emeld.
kor_rajzol()
). Egy objektum metódusa általában az objektum változóival dolgozik (statikusan hozzá van kötve), míg egy függvény a kapott paramétereivel. Ez utóbbi számomra jóval rugalmasabb megoldásnak tűnik. Természetesen egy metódust is meg lehet pusztán bejövő paraméterekkel valósítani, de akkor az objektum ismét csak névtérré silányul. Az OOP?Amire nem reagálok, azt elfogadom.
Egységbezárás
Felhő is ugyanezt állítja.
Öröklődés
Amikor bemutatják az elvet, miért mindig ilyen Móriczka-szintű példákat hoznak, mint mondjuk alakzatok, állatok és társaik? Ezek egyáltalán nem életszerű dolgok, ráadásul már ezen a szinten is megjelennek a problémák. Például vegyük az alakzatokat, ha mondjuk vannak a négyzetek és a téglalapok, akkor melyik legyen a szülőosztály? Lehet-e mindent hiearchiába szervezni, és van-e értelme?
A másik, ami felmerül, hogy ha öröklődést használunk, akkor minél többszörös, annál átláthatatlanabb lesz a kód, ugyanis amikor ránézünk egy példányosított objektumra, egyesével kell végigszemezgetnünk a tulajdonságait és a metódusait, hogy az öröklődési láncban melyik ősétől származik.
Maradjunk az alakzatos példánál, tegyük fel, hogy területet kell számolni: ekkor jóval egyszerűbb és átláthatóbb, ha egy függvényen belül használunk elágazásokat:
if (alakzat.tipus === 'negyzet') {
return alakzat.szelesseg * alakzat.szelesseg;
}
if (alakzat.tipus === 'teglalap') {
return alakzat.szelesseg * alakzat.magassag;
}
}
Jól látod :)
Az alakzatos példa amiben mozgatás van, - én arra gondolok -, az absztrakt osztály bemutatására kitűnő, hisz minden alakzatot ugyan úgy mozgatunk, de tök máshogy rajzoljuk ki, és hogy éppen hogyan az pont nem érdekes akkor amikor a mozgatást implementáljuk. Pont nem jó, hogy a pontból származtatják a kört, vagy a négyzetet, ez pont(érted??!! pont) hülyeség.
A példád nekem érdekes. Én biztos, hogy jobban örülnék annak, hogy ha a négyzet vagy a téglalap definíciójánál találnám meg, hogy hogyan kell kiszámolni a területét, mint a területszámolásnál találnám meg alakzatokra lebontva ugyan azt.
Én elképzelek 10+ alakzatot 10+ függvényt, melyek közül hol van, hol nincs implementáció, és máris elborzadok. Belegondolok abba, hogy ha hozzá kell adni egy új alakzatot, akkor hány fájlba kell belenyúlni a helyett, hogy hozzáadnánk egy fájlt, ami minden ilyen infót tartalmaz. Esetleg abba, hogy három kolléga, három különböző alakzatot kódol, és a munkaidejük nagy része az igazából nem létező konfliktok feloldásával telik, mivel a kód különböző részein ugyan azokat a sorokat kell, hogy módosítsák, ahelyett, hogy mindhárman hozzáadnának három különböző nevű fájlt, mindenféle konflikt nélkül.
Ez persze ne tartson vissza, hisz ez én vagyok. Ha neked így jobban átlátható, hát használd így.
Úgy gondolom, hogy az OOP-s eszközökhöz, lévén szabványosak sokkal könnyebb IDE támogatást fejleszteni, mint egy sajátos (bár lehet zseniális) logikával ellátott kódhoz. (ilyenben volt részem, én túlélem, de láttam embert, nem is egyet, cégtől ezért kilépni.)
Persze mindez a JavaScript témakörön belül nem biztos, hogy releváns. :D
Öröklődés
Amit leírsz, azzal egyelőre nem foglalkozom, mert a legfőbb kérdést kihagytad: hol érdemes öröklődést használni? Pontosan mit mondasz el a nebulóidnak, miután megmutattad nekik a procedurális programozást, és áttértek az OOP-re, hogy miért is érdemes ezzel dolgozni (öröklődés és/vagy OOP)?
Már jóideje programozok kliens- és szerveroldalon, de sosem volt szükség az OOP absztrakciójára, pedig voltak egyszerű és összetett alkalmazások is. Hogy lehet, hogy egyszerű adatszerkezetekkel és függvényhívásokkal meg tudtam mindent oldani?
Már jóideje programozok
Nagyon egyszerű, mert mindent meg lehet oldani struktúráltan OOP nélkül is, csak nem biztos, hogy érdemes.
Kérdés
Kérdés
Válasz
Pontosan
Én is procedurálissal kezdtem, de amikor megismerkedtem az OOP-vel egyből sokkal természetesebb közegben mozogtam. Nem arról szól, hogy jobb vagy rosszabb a másiknál. Én ebben vagyok hatékonyabb, jobban átlátom, jobban tudok vele tervezni. Te meg nem ennyi.
Te a `function terulet(alakzat)`-on belül halmozod az IF-eket, én az alakzatokat halmozom, amelyek mindegyike rendelkezik saját `function terulet()` metodussal.
Megközelítés és szemlélet kérdése. Kinek mi fekszik jobban.
Idézet a Cápa 1. részéből:
- Végülis, csak a tenger felől nézve sziget.
// nem szó szerinti idézet
Alapból OOP, mivel jóval
"Pontosan mit mondasz el a
Ha nincs rá igényük, és nem is tudom felkelteni rá az igényüket akkor igazából semmit, mert teljesen felesleges.
"A betonra szort magvakat elfújja a szél"
Egyébként pedig a kérdésedre válaszolva:
Az öröklés akkor jó, amikor egy magasabb absztrakciós szinten kell megfogalmazni valamit, és a konkrét megvalósításoknál pedig ezzel már nem akarsz foglalkozni.
pl. egy űrlap feldolgozás az úgy néz ki, hogy megnézzük, hogy jött-e adat, aztán megnézzük, hogy valid-e és utána pedig feldoglozzuk az űrlapot, egyébként meg kiíratjuk.
Ezt az absztrakciót beleteszem egy ős osztályba, és utána minden űrlapot innen származtatok, csak éppen a valid-e függvényt, és a feldolgozzuk részt kell implementálnom a leszármazottban.
Ezzel le tudom írni azt, hogy az űrlap feldolgozás hogyan néz ki, és az egyes konkrét űrlapok miben különböznek az általános űrlapfeldolgozástól.
Az IDE meg nem csak hogy szól, hogy hé-helló, ha formot akarsz akkor két függvényt kell implementálnod, hanem még oda is tolja a kód mintát, amit egy procedurálisan szervezet kód esetén nem nagyon láttam még működni.
Nekem ez így jó, nekem erre szükségem van, mert átlátható és könnyen érthető lesz tőle a kódom.
Te egy ilyet procedurálisan hogyan csinálnál meg?
(picit zavar a nebuló kifejezés, mert egyfajta degradáló jelzőnek gondolom, holott legtöbbször bizonyos területen nálam sokkal képzettebb és tapasztaltabb embereknek tartok oktatást.)
picit zavar a nebuló
Az űrlapok ellenőrzését egy függvénnyel szoktam megoldani, aminek paraméterként átadok egy tömböt, amiben a bejövő értékek és az elvárt formák vannak, ezeken végigiterálok, és a végén visszaadok egy tömböt a hibás elemekkel és a hibakódokkal/hivaüzenetekkel. Pl.
'email' => array(
array('kotelezo' => 1),
array('formatum' => 'email'),
array('adatbazis' => 'nem_regisztralt'),
),
'nev' => array(
array('formatum' => 'regexp', 'regexp' = '//')
),
);
$eredmeny = urlap_ellenoriz($bejovo_adatok);
Tök jó példa. Ha jól értem,
Ha jól értem, akkor minden egyes alkalommal újraimplementálod az űrlap feldolgozást, mivel nincs eszközöd rá, hogy ezt az absztrakciós szintet kezeljed.
Hát ha mondjuk azt írod, hogy koletezo akkor a szerkesztő pirossal aláhúzza, és nem kell debuggolnod, hogy kiderüljön hol a hiba.
pp
Újra?
1, reguláris kifejezés
2, van-e ilyen cím az adatbázisban
Regisztrációnál, ugye, még nem lehet a cím az adatbázisban, míg bejelentkezésnél pedig ott kell lennie. A kettőben közös a reguláris kifejezés, ezt ki tudom rakni egy változóba, és akkor egyszer kell csak megírni:
array('kotelezo' => 1),
array('formatum' => 'email')
);
(...)
$bejovo_adatok = array(
'email' => array_merge($global_email_ell, array(
array('adatbazis' => 'nem_regisztralt'),
)),
);
De ez már részletkérdés.
Ez jó példa arra, hogy mit
Sajnálom, hogy nem tudom, számodra is érthetően megfogalmazni azt amit mondani akarok - mert láthatóan nem megy át - és félek itt a fórum keretei között nem is leszek rá képes.
pp
Nem írtam sehol, hogy
Azért
Sajnálom, ha úgy érzed, hogy meg se próbáltam.
Arra gondolsz, hogy a Maxwell egyenletek úgy ahogy vannak értelmetlen hülyeségek, puszta úri huncutság?
pp
Az IDE meg nem csak hogy
Nagyon jó a példád. Nyilván a
Amit te csinálsz az struktúrált programozás, az objektumnak metódusai vannak, te viszont le akarod redukálni egy adatstruktúrára úgy, hogy kiemeled a metódusait függvényekbe. Nincs ezzel semmi baj, bizonyos esetekben tényleg ez a jobb módszer. Az előnye, hogy egyszerűen be lehet vezetni vele új függvényt, mert csak egy helyen kell hozzányúlni a kódhoz. A hátránya, hogyha új típust akarsz bevezetni, pl kört, akkor az összes ilyen függvényedhez hozzá kell nyúlnod ezer helyen, míg oop-nél csak egy új osztályt kell csinálnod, ami implementálja az Alakzat interface-t, aminek ugye van terület() metódusa. (Js esetében csak üres Alakzat osztályból örököltetés a megoldható, ha instanceof-al akarunk ellenőrizni.) Feladat függő, hogy struktúráltan vagy objektum orientáltan érdemes e megoldani a problémát. Ezt sokan nem látják, gyakorolni kell hozzá nem keveset, hogy zsigerből jöjjön elsőre a jó.
Nem lehet mindent hierarchiába rendezni, sokszor bejön, hogy többszörös öröklés meg hasonlók kellenének, meg ugye itt is gráfról van szó, nem fáról. Általában ahol lehet, érdemes kompozíciót használni öröklődés helyett. Legalábbis azt mondják. Én vegyesen használom, érzésre ahogy jónak látom. Kompozícióhoz lehet használni egy segéd osztály átadott példányát, vagy traits-et is. (Js-nél nyilván csak a példány átadás jön szóba.)
Off:
Sokadik alkalom már, hogy felhozod ezt az öröklődés témát oop-vel kapcsolatban. Úgy látszik nehezen jutnak el hozzád a válaszok, mert ugyanezt már számtalanszor leírtam én is, meg sokan mások is.
Absztrakció
Lássuk, hogyan fejlődött a programunk az ügyfél kérésének megfelelően! A függvénydeklarációkat kihagyom (absztrakciózom).
V1
V2
A következő lépésben bonyolódik az ügy, az egyik elágazáson belül újabb feltétel képződik futásidőben, ami alapján egy belső elágazásra futunk.V3
Végül azt kérte az ügyfél, hogy atedd_amazt
függvény akkor is fusson le, ha nem teljesül a feltétel.Tanulság
Az OOP egyik alapvető eleme az absztrakció, egyszerűsítés. Programunkat úgy írjuk meg, hogy élünk bizonyos előfeltételezésekkel, így nem kell minden esetet lekezelni. A fenti Móriczka-példában abból indultunk ki, hogy objemtumaink típusa mindig egyféle értéket vehet fel, ennek köszönhetően programunk valóban jól olvasható volt.Amint változnak a feltételek, a kódot drasztikus mértékben kell módosítani. Míg a procedurális változatban elég volt az
if
-eket átírni, az OOP-s változatban elég volt egy új kérés, és kénytelenek voltunk bevezetni először egy gyárat, majd pedig a kompozit mintára építeni a programunkat.Kétszer kellett átírni a kódot. Hogy ez ne hangozzék túl drasztikusan a menedzsment és a megrendelők fülében, ehelyett célszerűbb inkább a politikailag korrektebb refaktorálás szót használni. Emellett a program hosszabb lett, mint a procedurális változatnál, valamint komplexebb is, ami növeli a létrehozás és a karbantartás költségeit is.
Következtetés
A fentiek alapján egyértelmű, hogy OOP-t statikus környezetben lehet célszerű használni, például olyan helyen, ahol jóideje nem változott a program, már átlátunk minden összefüggést. Új projekteknél viszont rossz választás lehet, hacsak nem vagyunk a szakterület specialistái (pl. zsinórban CMS-eket gyártunk), mert, mint látható, bizonyos típusú kérések (a példában egy egyszerű ÉS kapcsolat) boríthatják a bilit.Kétszer kellett átírni a
Most komolyan, ki gyárt
"Ha a ciklomatikus
Nem hiszem.
Komplexitás
Ezek után kérdés, mi is a ciklomatikus komplexitás értelme.
akkor a fő programom
Akkor az
egyszeru()
komplexitása lesz magas, szóval nem ;).Ha megnézed, az objektum-orientált változat csak egyszerű függvényekből építkezik.
Ha megnézed, az
Ezzel szemben a procedurális programnál a legegyszerűbb téglákból építkezünk: változó, ciklus, feltétel, függvény. Jóval kevesebb mintát kell ismerni, leginkább azt kell megtanulni, hogyan kell jól elnevezni a fentieket, és akár profi, akár laikus néz rá a programra, tudni fogja pontosan, mit csinál.
Ezek után döntse el mindenki maga, melyiket választja.
Igen, de ha összeadjuk a
Ezzel szemben a procedurális programnál a legegyszerűbb téglákból építkezünk: változó, ciklus, feltétel, függvény. Jóval kevesebb mintát kell ismerni, leginkább azt kell megtanulni, hogyan kell jól elnevezni a fentieket, és akár profi, akár laikus néz rá a programra, tudni fogja pontosan, mit csinál.
Én (mint mérnök) nem
Amíg nekem elég volt pár if-et módosítani, az OOP kódot kétszer kellett újraírni, hogy az absztrakciót meg lehessen tartani (ceremónia). Magyarul nemcsak komplexebbé vált a program, hanem több idő is ment el a munkával.
Az oo kódban sincs
Ha az absztrakciót rosszul választod meg már az elejétől, akkor később nyilvánvalóan át kell írni. Kell valamekkora előrelátás, különben nem lesz elég rugalmas, és eltörik a változtatáskor. Ha nagyjából képben van az ember, hogy mit csinálnak az osztályok, akkor az esetek többségében képes olyat készíteni, ami nem esik szét idővel. Ha nem, akkor vagy túl minimál (pl a te első példád), vagy túltervezett lesz (ezer osztály teljesen feleslegesen).
Csak hogy a te példáddal
Ha előre látszik, hogy néhány hasonló dolgot egymás után hívunk meg sorozatban, akkor eleve olyan futtatókörnyezetet tervezünk, ami erre képes, nem először egyenként elkezdjük hívogatni, utána meg kompozitba rakjuk őket. A kompozit inkább arra való, ha függőségként akarjuk betenni valahova úgy, hogy az interface megmaradjon. Jelen esetben erre nincs kifejezetten szükség.
Nem cáfoltál meg sokat.
Sure, te viszont újraírásról
Én (mint mérnök) nem
+1, nekem is ugyanez jött le, bár szerintem procedurálisan nem oldható meg a laza csatolás olyan kényelmesen, mint oo esetében.
var tovabbi_feltetel =
Hogy írnád le az absztrakció
A következetesen rossz következtetéseket be lehet tudni az OOP tapasztalat hiányának, de úgy érzem a legalapvetőbb fogalom, amire felfűzöd az elméleted sincs a helyén.
Az absztrakció
Az információ, a feltételek pedig szanaszét vannak szórva a kódban. Ez akkor hátrány, amikor a nagy képet szeretnéd megérteni, mert akkor ezer helyről kell összevadásznod, amikor meg szeretnéd érteni.
Más kérdés, hogy nem
Érdekes, mivel én v2-nél azt
Én a tedd_ezt()-be nyúltam volna bele és oda tettem volna be ezt az elágazást. Ezzel ugye azt is nyerem, hogy ha valaki máshol használja a tedd_ezt() akkor a tedd_ezt() tevés megváltozása miatt ott is jól tenné ezt. Persze csak akkor, ha a tedd_ezt() absztrakció még továbbra is igaz lenne arra amit csinál az adott fv, ha nem, mert ezt még mindig ugyan úgy kéne tenni, de az elágazásban lévő kód változott meg, akkor a te példádat követtem volna és tedd_ezt_igy() függvényt alkottam volna meg, ami a feltételtől függően hívja a tedd_ezt() függvényt.
Szerencsére már itt kiabsztraháltad a tedd_amazt() egy függvénybe így v3-ban már könnyű dolgot volt.
Mint ahogyan a programozás egyik alapvető eleme is. Lehet, hogy az én bajom, de én már a gépi kódban is absztraháltam, és igen megörültem, amikor a C-ben megvoltak azok a nyelvi eszközök, amiket az assembly-ben hiába kerestem, és még boldogabb voltam, amikor C++-ban pedig olyan nyelvi elemek (OOP) jelentek meg, amivel még magasabb szintre emelhettem az absztrakciókat. Mert mint ahogyan te is írod, meg lehet mindent csinálni C-ben, és meg is csináltam. Na az a kód tényleg átláthatatlan volt :)
Mint ahogyan mindkét kódot, az általad procedurálisnak nevezett kódot is. Vagy akkor miért változott meg a függvény neve? Nyilván azért mert más csinál.
Tényleg nem látom a különbséget. Mind a két esetben ("procedurális" és "OOP") ugyan úgy módosítani kellet a kódot.
A több kód az lényegtelen kérdés, de ezt korábban már mondtam is, egy jó IDE-vel nem lesz több munka a fenti kódváltoztatás begépelése, pláne akkor, ha egy már ismert mintát használsz, ami adott esetben a keretrendszered/használt nyelv része is.
Ami sántít a példádban az az, hogy a teáltalad procedurálisnak hívott kód, mind OOP-ben mind procedurális kódban helye van, míg az oop kódod pusztán felesleges absztrakciót tartalmaz, amiről megállapítod, hogy felesleges.
Nincs ezzel baj
Nincs ezzel baj, nem kell mindenkinek szeretnie a húst. Mi húsevők elfogadjuk ezt. Nem fogjuk emiatt őt lenézni, vagy megbélyegezni, csak idővel nem fogjuk már többet meghívni a BBQ-partikra :)
Rofle :D
Én a tedd_ezt()-be nyúltam
tedd_ezt()
függvényttedd_ezt_igy()
-re, de valószínű, hogy elgépelés.Tehát a V3 kód helyesen:
if (feltetel) {
var tovabbi_feltetel = vizsgalat_eredmenye();
if (tovabbi_feltetel) {
tedd_ezt();
}
else {
tedd_amazt();
}
}
else {
tedd_emezt();
tedd_amazt();
}
Az OOP kódban abból indultam ki – Blaze ajánlására –, hogy a fő programban csak azt lássa a programozó, mi történik valójában (egymás után egy vagy két metódushívás). Az első verzióban ez egy egyszerű metódushívás. A második verzióban a feltételek megváltozása miatt be kellett vezetni egy gyárat, amelyik előállítja a megfelelő objektumot. A harmadik verzióban a gyár mellé bejött a kompozit osztály is.
Nem az a lényeg, hogy mennyit kell gépelni, mert egy ilyen Móriczka-példában ez valóban nem releváns. De amikor ezt egy akár többszáz vagy többezer soros kódbázison kell elvégezni, mert az üzleti logika ezt kívánja meg, akkor az már jóval macerásabb.
A procedurális kód jóval egyszerűbb maradt végig, míg az OOP-s példában a feltételek kiértékelése ki lett szervezve egy-egy programozási mintába. Ha az Occam borotvája elvből indulunk ki, mindenképp a procedurális változatot érdemes használni.
bind
Hát pl ha prototípusos
Kétségtelen,
Jó lenne, ha még prototípusos öröklődésnél is működne automatikusan ez a self-bind, szerinted miért nem lett ez a nyelvbe implementálva? Lenne valamilyen hátránya?
Elméletileg kérdem csak, most nem jut eszembe trükk, amivel ezt meg lehetne oldani.