ugrás a tartalomhoz

Objektumorientált programozás JavaScriptben

Poetro · 2015. Ápr. 21. (K), 00.40

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.

 
Poetro arcképe
Poetro
1998 óta foglalkozik webfejlesztéssel, amikor is a HTML és a JavaScript világa elvarázsolta. Azóta jópár évet dolgozott reklámügynökségeknél, és nemzetközi hírportálok fejlesztésével. Legfőképpen Drupal fejlesztéssel, site buildinggel és JavaScripttel, azon belül is jQuery-vel és Node.js-sel foglalkozik.
1

Köszi a cikket! Hasznos volt!

webproghu · 2015. Ápr. 21. (K), 10.07
Köszi a cikket! Hasznos volt!
2

thx

DRobi · 2015. Ápr. 21. (K), 16.50
Rég volt cikk, de ez az új nagyon hasznos.
Remélem lesz kedved és időd folytatni.
Köszönjük!
3

Köszönöm én is.

Szuperjég · 2015. Május. 1. (P), 13.17
A prototípusok működését boncolgatom éppen, a cikk segít tisztábban látnom a témát. Valamint jó néhány szakkifejezést idáig csak angolul ismertem.
4

Kérdések

Hidvégi Gábor · 2015. Júl. 15. (Sze), 11.15
Alaposan összeszedted a dolgokat, viszont a leírtak számtalan kérdést felvetnek, amelyeket félkövérrel jelöltem meg, ezekre szeretnék válaszokat kapni!

Ö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 az extends-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 egy objektum.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 a function.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?
6

Védelem

Poetro · 2015. Júl. 15. (Sze), 14.59
Az OOP azért alapvető védelemmel szolgál egy weboldalon. Az oldalon ugyanis általában nem csak a te kódod fut, hanem még jó pár más szolgáltatás is. Ha csak úgy létrehoznánk minden féle néven globális függvényeket, akkor azokat bárki felülírhatja, ezzel megváltoztatva a programunk működését. Tegyük fel, hogy használunk egy slice nevű függvényt, ami azt csinálja, mint az Array.prototype.slice és a String.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).
7

Az ellen nem véd, deviszont...

Hidvégi Gábor · 2015. Júl. 15. (Sze), 15.05
Array.prototype.slice = function() { alert('hehe'); };
8

"Véletlen"

Poetro · 2015. Júl. 15. (Sze), 15.27
Azért a fentihez kicsit több kell a véletlennél.
9

Életszerűtlen

Hidvégi Gábor · 2015. Júl. 15. (Sze), 15.47
A felhozott példád nem valami jó. Egyrészt, mint fentebb illusztráltam, bármely objektum bármely publikus tagját csak az nem írja felül, aki nem akarja, másrészt egy sajat_objektum.slice() nevű metódus semmivel sem védettebb, mint egy sajat_fuggvenytaram_slice() nevű függvény.
15

Ha például az alapobjektumon

MadBence · 2015. Júl. 15. (Sze), 18.37
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.
Ha van egy függvényed, és változtatsz rajta valamit, akkor ugyanúgy le kell ellenőrizned minden más függvényt ami meghívja. Ez nem a paradigma problémája.
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.
Megint ugyanaz. Amúgy létezik egy verziózás nevű dolog, nem tudom hallottál-e már róla.
Hol érdemes öröklődést használni?
Kiszedtem neked a Wikipediáról a lényeget: "It is a mechanism for code reuse"
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.
Erre a kijelentésre példát is tudnál mutatni?
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.
A cikk a Composition over Inheritance-ről szól. Ebben igazat adok, az öröklődés gyakran kényelmetlen, szerencsére JS-ben a mixin mintával viszonylag fájdalommentesen meg lehet kerülni a problémát (vagy lehet kompozíciót is használni).
ES6-ban miért vezették be a class-t és az extends-et?
Kevesebb boilerplate kód, egységes szemantika (sokan egyszerűen rosszul használták az öröklést).
Ha az öröklődés kiesik, mi az értelme az objektumoknak?
Dynamic dispatch például.
miben több/jobb attól, mintha normál függvényeket hívnánk egy prefix-szel?
Megint dynamic dispatch. Egyébként ne ragadj le a szintaktikánál, a foo(adat, param1, param2) ugyanúgy objektum-orientált mint az adat.foo(param1, param2).
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.
Miért ne tartozhatna?
Erre mutat rá például a function.call() és a function.apply(), ami nyilvánvaló nyelvi bűz (language smell).
Pont a call és apply oldja meg, hogy egy függvénynek te tudod beállítani a kontextusát... Amúgy ez mitől nyilvánvaló language smell?
Miért kell az adatokat és a függvényeket összeházasítani?
Ha valami nem világos, forduljunk bátran a wikipediához!
The purpose of encapsulation (to classify) can be summarized to the following: to reduce collisions of identically named variables and to group together related methods (functions) and properties (variables) to comprise an object of class (like a family). This pattern of practice helps make source code with hundreds or thousands of lines of code more understandable and workable

Ha nem tudjuk megvédeni azt, ami a mienk, akkor beszélhetünk-e OOP-ről?
A privát tagváltozók nem azért vannak, mert azok titkosak, vagy meg kéne őket védeni valami gonosz felhasználótól/programozótól. A privát adattagók/metódusok azért vannak, hogy maga az objektum belsőleg operáljon velük, kívülről nem szabad őket bántani, mert nem részei a objektum publikus interfészének. Ebből a szempontból egy aláhúzás prefix, vagy egy @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.
van-e egyáltalán OOP a javascriptben?
Szerintem van (a fenti válaszaim tükrében mindenképpen).
miért ne válasszuk inkább a jóval egyszerűbb függvényekkel való munkát?
Karbantarthatóság, átláthatóság, modularitás, a szokásos mantrák, ami elhangzik minden threadben, ahol nekiállsz puffogni.
16

Köszönöm a válaszaidat, bár

Hidvégi Gábor · 2015. Júl. 15. (Sze), 22.11
Köszönöm a válaszaidat, bár kissé nyeglének érzem őket. Lássuk:

Ha van egy függvényed, és változtatsz rajta valamit, akkor ugyanúgy le kell ellenőrizned minden más függvényt ami meghívja. Ez nem a paradigma problémája.
Az első pár kérdésedre választ az alábbi linkeken találhatsz:
banki példa
játékos példa
Jedi-s példa

igazat adok, az öröklődés gyakran kényelmetlen, szerencsére JS-ben a mixin mintával viszonylag fájdalommentesen meg lehet kerülni a problémát
Látod, ezért kérdeztem, hogy mikor érdemes használni öröklődést. Azt írod, hogy gyakran, amiből arra következtetek, hogy legalább az esetek 50%-ában. Mik azok a használati esetek, ahol nem okoz gondot?

Dynamic dispatch például.
És ez mikor jó? A területszámítós példámban nincs szükség ilyenre, bejön a paraméter, abból kiderítjük, hogy milyen a típusa, és a megfelelő ágban kiszámoljuk a területét.

Egyébként ne ragadj le a szintaktikánál, a foo(adat, param1, param2) ugyanúgy objektum-orientált mint az adat.foo(param1, param2)
Szerintem meg pont hogy mindkettő hívás procedurális.

Pont a call és apply oldja meg, hogy egy függvénynek te tudod beállítani a kontextusát... Amúgy ez mitől nyilvánvaló language smell?
Javascriptben minden metódus és adattag egy darab objektumhoz tartozhat, ezt oldod fel ideiglenesen ezzel a két függvénnyel. PHP-ban a függvények mindenki macái, és nincs szükség ilyen kerülő utakra. Egyébként a legtöbbet az Array.prototype.slice.call(arguments)-et lehet látni, ami azt mutatja meg, hogy rosszul lett megtervezve az arguments 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?

»Miért kell az adatokat és a függvényeket összeházasítani?«
Ha valami nem világos, forduljunk bátran a wikipediához!
Ha az adatok és függvényeik össze vannak házasítva, akkor erős köztük a kötés. Álljon itt egy egyszerű példa: van egy Kör objektumom, és annak van egy rajzoló metódusa:

function Kor(x, y, sugar) {
  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:

function kor_rajzol(x, y, sugar) {
  (...)
}

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:

$adatok = query("SELECT * FROM felhasznalok");
foreach ($adatok as $sor) {
?>
  <td><?php print $sor['mezo1']; ?><br><?php print $sor['mezo2']; ?></td>
<?php
}

A privát tagváltozók nem azért vannak, mert azok titkosak, vagy meg kéne őket védeni valami gonosz felhasználótól/programozótól. A privát adattagók/metódusok azért vannak, hogy maga az objektum belsőleg operáljon velük, kívülről nem szabad őket bántani
A két mondatod között ellentét feszül. Akkor mégiscsak meg kell védeni őket, mert különben hibás működés léphet fel, nem?

»miért ne válasszuk inkább a jóval egyszerűbb függvényekkel való munkát?«
Karbantarthatóság, átláthatóság, modularitás
Ha a javascriptben az objektumok csak névterek, akkor mitől karbantarthatóbb, átláthatóbb, modulárisabb a velük való munka, mint függvényekkel?

objektum.metodus(parameter);

versus

fuggveny(adat, parameter);

Számomra nagyon egyformának tűnnek, nem látom az objektum egyértelmű előnyét.
17

Mire számítasz ettől a

BlaZe · 2015. Júl. 15. (Sze), 23.49
Mire számítasz ettől a threadtől? Ugyanezeken a dolgokon már átrágtuk magunkat százszor, és időről időre újra előkerül ugyanaz. Kaptál sok minőségi választ az évek alatt, mégis megint itt vagyunk. Amiket hozol OOP példákat, azok valójában nem azok. Ez a körös példa is nagyon rossz. Egy olyan példával próbálod bemutatni, hogy rossz/értelmetlen az OOP, amiről ordít hogy hibás. Ezt te is észreveszed, hiszen kiemeled. Láttál már olyan kört, ami lerajzolta magát? Nyilvánvalóan az a megoldás, hogy van egy objektumod, ami tudja hogy kell kiszámolni a kör pontjait, meg egy másik, ami annak az eredményét rá tudja rajzolni pl egy canvasra. Utána ezt a rajzoló objektumot fel tudod használni bármilyen alakzat lerajzolására. Separation of concerns, single responsibility és egyéb menő vezényszavak.
18

Nyilván, ahogy fejlődik egy

inf3rno · 2015. Júl. 16. (Cs), 02.27
Nyilván, ahogy fejlődik egy projekt, úgy kerülnek az ilyen dolgok refaktorálásra, újragondolásra. Ha el se próbál indulni valaki oop-vel, akkor nyilván megragad az ilyen szintű példa projekteknél. Mi is így kezdtük. Nincs ezen mit csodálkozni. Azért csodálom a türelmed, hogy az egészet végigolvastad. Én 2 sor után feladtam... :-)
20

Nem erőltetném a dolgot, ha

Hidvégi Gábor · 2015. Júl. 16. (Cs), 09.31
Nem erőltetném a dolgot, ha valóban kielégítő válaszokat kaptam volna, és nem lennének nyilvánvaló ellentmondások a témában, mint például ebben a cikkben, amikre nem csak én mutatok rá, hanem millió írást lehet találni rájuk a neten.

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.
function Kor(x, y, sugar) {
  this.x = x;
  this.y = y;
  this.sugar = sugar;
}
Kor.prototype.pontok = function kor_pontok() {
  var lepeskoz = 2 * Math.PI / 20;
  var pontok = [], x, y, theta;
  for (theta = 0; theta < 2 * Math.PI; theta += lepeskoz) {
    x = this.x + this.sugar * Math.cos(theta);
    y = this.y - this.sugar * Math.sin(theta);
    pontok.push([x, y]);
  }
  return pontok;
}

function Rajzolo(vaszon) {
  this.vaszon = vaszon;
}
Rajzolo.prototype.rajzol = function rajzol(pontok) {
  for (var i = 0; i < pontok.length; i++) {
    this.vaszon.lineTo(pontok[i][0], pontok[i][1]);
  }
}

function Fuggoseg_befecskendezes_tarto(kor, rajzolo) {
  this.kor = kor;
  this.rajzolo = rajzolo;
}
Fuggoseg_befecskendezes_tarto.prototype.getKor() {
  return this.kor;
}
Fuggoseg_befecskendezes_tarto.prototype.getRajzolo() {
  return this.rajzolo;
}

function Kor_rajzolo(fuggoseg_befecskendezes_tarto) {
  this.fuggoseg_befecskendezes_tarto = fuggoseg_befecskendezes_tarto;
}
Kor_rajzolo.prototype.rajzol = function Kor_rajzolo_rajzol() {
  this.fuggoseg_befecskendezes_tarto.getRajzolo().rajzol(this.fuggoseg_befecskendezes_tarto.getKor().pontok());
}

//főprogram
var vaszon = canvas.getContext('2d');
var rajzolo = new Rajzolo(vaszon);
var kor = new Kor(100, 100, 200);
var fuggoseg_befecskendezes_tarto = new Fuggoseg_befecskendezes_tarto(kor, rajzolo);
var kor_rajzolo = new Kor_rajzolo(fuggoseg_befecskendezes_tarto);
Kor_rajzolo.rajzol();
versus
function kor_pontok(x, y, sugar) {
  var lepeskoz = 2 * Math.PI / 20;
  var pontok = [], px, py, theta;
  for (theta = 0; theta < 2 * Math.PI; theta += lepeskoz) {
    px = x + sugar * Math.cos(theta);
    py = y - sugar * Math.sin(theta);
    pontok.push([px, py]);
  }
  return pontok;
}
function rajzol(vaszon, pontok) {
  for (var i = 0; i < pontok.length; i++) {
    vaszon.lineTo(pontok[i][0], pontok[i][1]);
  }
}

//főprogram
var vaszon = canvas.getContext('2d');
rajzol(vaszon, kor_pontok(100, 100, 200));
Paul Graham szerint:
Object-oriented programming generates a lot of what looks like work. Back in the days of fanfold, there was a type of programmer who would only put five or ten lines of code on a page, preceded by twenty lines of elaborately formatted comments. Object-oriented programming is like crack for these people: it lets you incorporate all this scaffolding right into your source code.
26

Hm?

BlaZe · 2015. Júl. 16. (Cs), 13.32

interface PointRenderer {
  render(x, y);
}

class CanvasRenderer implements PointRenderer {
  ...
}

interface Shape {
  renderWith(PointRenderer renderer);
}

class Circle implements Shape {
  renderWith(PointRenderer renderer) {
    x = ...
    y = ...
    renderer.render(x,y);
  }
}

PointRenderer renderer = new CanvasRenderer(canvas);
new Circle(x,y,sugar).renderWith(renderer);
Hm? Mekkora meló, ha text fileba kell ezek után kiíratni a pontok koordinátáit? Vagy ha egyszerre canvasra is kell rajzolni, meg text fileba is? És kell-e collectionnel dobálózni? A példa kicsit buta, de látható mi a lényeg.
27

Több helyre

Hidvégi Gábor · 2015. Júl. 16. (Cs), 14.14
function szovegfajlba_kiir(pontok) {
  var tomb = [];
  for (var j = 0; j < pontok.length; j++) {
    tomb.push(pontok[j].join(','));
  }
  save(tomb.join(','));
}

function kikuld(hova, pontok) {
  if (hova === 'vaszon' || hova === 'vaszon_es_fajl') {
    var vaszon = canvas.getContext('2d');
    rajzol(vaszon, pontok);
  }
  if (hova === 'fajl' || hova === 'vaszon_es_fajl') {
    szovegfajlba_kiir(pontok);
  }
}

//főprogram

var pontok = kor_pontok(100, 100, 200);
kikuld('vaszon', pontok);
És kell-e collectionnel dobálózni?
A fentiek alapján egyértelmű (ha mindkét helyre ki kell nyomni az adatokat).

A példa kicsit buta, de látható mi a lényeg.
A te példádban hogyan oldanád meg, hogy akár két helyre is kimenjen?
29

A te példádban hogyan oldanád

BlaZe · 2015. Júl. 16. (Cs), 14.25
A te példádban hogyan oldanád meg, hogy akár két helyre is kimenjen?
Composite patternnel. És nem kell hozzá tömb sem. Se if halom. Átlátható, egyszerű, flexibilis.
36

Ha

Hidvégi Gábor · 2015. Júl. 16. (Cs), 15.00
Mi a baj az if-ekkel? Mi a baj a tömbökkel?
37

If: Megtöri a kód

BlaZe · 2015. Júl. 16. (Cs), 15.25
If: Megtöri a kód olvashatóságát. Egy picit is összetettebb kódnál sok ágat kell a fejedben tartani, hogy épp melyik eset is lehet.

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.
38

Stringly TypedA riff on

MadBence · 2015. Júl. 16. (Cs), 15.32
http://blog.codinghorror.com/new-programming-jargon/

Stringly Typed

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

Dynamic dispatch

Hidvégi Gábor · 2015. Júl. 19. (V), 15.50
Próbálom megérteni a dynamic dispatch-et. Tehát elvileg a következő kód:
//fő program
if (feltetel) {
  tedd_ezt();
}
else {
  tedd_emezt();
}
így fog kinézni OOP-vel:

Node ehhez az elején valahogy inicializálni is kell:
//osztályok deklarációja
function Ezt_tevo_osztaly() {}
Ezt_tevo_osztaly.prototype.tedd = function() {};

function Emezt_tevo_osztaly() {}
Emezt_tevo_osztaly.prototype.tedd = function() {};

var objektum = feltetel ? new Ezt_tevo_osztaly : new Emezt_tevo_osztaly;

//fő program
objektum.tedd();
Most itt mit nyertem? A fő program valóban egy soros, de az if-et nem spóroltam meg, csak máshova került.
70

Ezek általában nem a fő

inf3rno · 2015. Júl. 19. (V), 20.44
Ezek általában nem a fő programban kapnak helyet, hanem beinjektálják őket alsóbb szintekre. Így ahova bekerül egy ilyen objektum, annak csak azt kell tudnia, hogy implementál egy bizonyos interface-t, azt nem kell tudnia, hogy hogyan. A struktúrált megoldásban vagy leküldöd a feltételt, mint paramétert alsóbb szintre, vagy closure-al küldöd át az aktuális függvényt, mint paramétert alsóbb szintre, ami valami hasonló, mint a dd-s módszer.
77

Az a baj, hogy nagyon

BlaZe · 2015. Júl. 19. (V), 22.20
Az a baj, hogy nagyon implementációban gondolkodsz mindig. Az OOP nem az implementációról szól, hanem az abszrakciókról és az objektumok közötti contractokról.

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.
79

Inplementáció

Hidvégi Gábor · 2015. Júl. 20. (H), 09.00
Természetesen, hisz gondolkodhatok absztrakciókban, de előbb-utóbb úgyis meg kell őket valósítani : )

Tegyük fel, hogy az ügyfél újabb kéréssel érkezik:
//fő program
if (feltetel) {
  var tovabbi_feltetel = vizsgalat_eredmenye();
  if (tovabbi_feltetel) {
    tedd_ezt_így();
  }
  else {
    tedd_amazt();
  }
}
else {
  tedd_emezt();
}
Ebben az esetben hogy fog kinézni az OOP program? Újabb osztályt kell származtatnom? Mi van, ha a vizsgálat eredménye csak futásidőben tud kiderülni, akkor mikor tudom példányosítani az objektumom? Két helyen kell?
//osztályok deklarációja
function Tevo_osztaly() {};
Tevo_osztaly.prototype.tedd = function() {};

function Ezt_igy_tevo_osztaly() {}
Ezt_tevo_osztaly.prototype = Tevo_osztaly;
Ezt_tevo_osztaly.prototype.tedd = function() {};

function Amazt_tevo_osztaly() {}
Amazt_tevo_osztaly.prototype = Tevo_osztaly;
Amazt_tevo_osztaly.prototype.tedd = function() {};

function Emezt_tevo_osztaly() {}
Emezt_tevo_osztaly.prototype.tedd = function() {};

//fő program
var objektum;

var tovabbi_feltetel = vizsgalat_eredmenye();
if (feltetel) {
  objektum = tovabbi_feltetel ? new Ezt_igy_tevo_osztaly : new Amazt_tevo_osztaly;
}
else {
  objektum = new Emezt_tevo_osztaly;
}

objektum.tedd();
A változásnak köszönhetően át kellett szervezni a programot, hisz az inicializációs részből ki kellett venni az objektum példányosítását, és át kellett tenni a fő programba, hisz egy feltétel csak futásidőben derül ki.
89

Természetesen, hisz

BlaZe · 2015. Júl. 20. (H), 21.48
Természetesen, hisz gondolkodhatok absztrakciókban, de előbb-utóbb úgyis meg kell őket valósítani :)
Ha neked ez jó, akkor neked nem való az OOP. Az a belépő, hogy megértsd: egy programot nem implementáció köré szervezünk, hanem egy megfelelő absztrakciós szintekkel megtervezett program részeinek elkészítjük az implementációját. Persze ez nem csak OOP programokra igaz, de azokra különösen.
90

+1

inf3rno · 2015. Júl. 20. (H), 22.36
+1
93

+1

bamegakapa · 2015. Júl. 20. (H), 23.54
És kitárul egy új világ.
94

Jelen esetben a Factory

bamegakapa · 2015. Júl. 21. (K), 00.03
Jelen esetben a Factory pattern segíthet rajtad (illetve inkább azokon, akik majd később olvassák ezt a témát), ha jól értem a problémát.

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.
95

Ha van autocomplete, akkor

inf3rno · 2015. Júl. 21. (K), 00.24
Ha van autocomplete, akkor mindegyiknél keveset kell gépelni. :D
81

Sajnos nem igazán jó a

MadBence · 2015. Júl. 20. (H), 09.47
Sajnos nem igazán jó a példád, sokkal jobban szemlélteti a működést ez:
if (...) {
  adat = ...
} else {
  adat = ...
}

// valamikor később

function foo(adat) {
  if (adat.type == 'alma') ...
  else if (adat.type == 'korte') ...
  else ...
}

foo(adat);

// (alternatíva, de ugyanaz)

if (adat.type == 'alma') {
  foo_alma(adat)
} else if (adat.type == 'korte') {
  foo_korte(adat)
} else {
  ...
}

class Foo {
  foo() {
    ...
  }
}

class Bar {
  foo() {
    ...
  }
}

if (feltetel) {
  adat = new Foo();
} else {
  adat = new Bar();
}


// valamikor később

adat.foo();
Azaz valóban megspórolsz egy if-et, az egyes osztályok implementációja egy helyen van
42

Ezt szerintem te sem gondolod

inf3rno · 2015. Júl. 16. (Cs), 16.41
Ezt szerintem te sem gondolod komolyan, hogy minden alkalommal kiírod, hogy "vaszon_es_fajl". Legalább tedd valami konstansba, mert kb 10 perc után eltöri a kódot, ha elírod.
30

Élő példa

Hidvégi Gábor · 2015. Júl. 16. (Cs), 14.30
Tudnál mondani példát, hogy nálatok milyen típusú objektumoknál használtok öröklődést, és az hány szintű?
39

Ahol a leszármazott

BlaZe · 2015. Júl. 16. (Cs), 15.37
Ahol a leszármazott viselkedése támaszkodik a szülőére, kiegészíti, specializálja azt. És annyi szintű, amennyi kell. Ennél pontosabb választ erre ne nagyon várj. Ez kb olyan kérdés, hogy milyen típusú feladatoknál használsz segédváltozót és hányat.
43

Kicsit refaktoráltam az oo

inf3rno · 2015. Júl. 16. (Cs), 17.22
Kicsit refaktoráltam az oo példádat, mert tisztán látszik rajta a hozzá nem értésed.

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:

var rajzKeszito = new RajzKeszito();
rajzKeszito.hasznaljVaszont({id: "vaszon"});
rajzKeszito.csinaljKort({x: 100, y: 100, sugar: 200});
rajzKeszito.rajzoldMeg();
versus a te struktúrált kódod:
 
    var canvas = document.getElementById("vaszon");
    var pontok = kor_pontok(100, 100, 200);  
    kikuld('vaszon', pontok);  
Szerintem ugyanott vagyunk. Nézzük meg hogy a kiküldést hogyan csinálja:

var RajzKeszito = Osztaly({
    constructor: function () {
        this.rajzolok = [];
        this.alakok = [];
    },
    hasznaljVaszont: function (tulajdonsagok) {
        var vaszonNode = document.getElementById(tulajdonsagok.id);
        var vaszon = vaszonNode.getContext("2d");
        var rajzolo = new VaszonraRajzolo(vaszon);
        this.rajzolok.push(rajzolo);
    },
    csinaljKort: function (tulajdonsagok) {
        var kor = new Kor(tulajdonsagok);
        this.alakok.push(kor);
    },
    rajzoldMeg: function () {
        this.alakok.forEach(function (alak) {
            this.rajzolok.forEach(function (rajzolo){
                rajzolo.rajzol(alak);
            }, this);
        }, this);
    }
});
vs

function kikuld(hova, pontok) {  
  if (hova === 'vaszon' || hova === 'vaszon_es_fajl') {  
    var vaszon = canvas.getContext('2d');  
    rajzol(vaszon, pontok);  
  }  
  if (hova === 'fajl' || hova === 'vaszon_es_fajl') {  
    szovegfajlba_kiir(pontok);  
  }  
}  
Nézzük meg, hogy a rajzolást és a pontok számolását hogyan csinálja:

/** @implements {Alak} */

var Kor = Osztaly({
    constructor: function (tulajdonsagok) {
        this.x = tulajdonsagok.x;
        this.y = tulajdonsagok.y;
        this.sugar = tulajdonsagok.sugar;
    },
    pontok: function () {
        var lepeskoz = 2 * Math.PI / 20;
        var pontok = [], x, y, theta;
        for (theta = 0; theta < 2 * Math.PI; theta += lepeskoz) {
            x = this.x + this.sugar * Math.cos(theta);
            y = this.y - this.sugar * Math.sin(theta);
            pontok.push({x: x, y: y});
        }
        return pontok;
    }
});

/** @implements {Rajzolo} */

var VaszonraRajzolo = Osztaly({
    constructor: function (vaszon) {
        this.vaszon = vaszon;
    },
    rajzol: function (alak) {
        var pontok = alak.pontok();
        pontok.forEach(function (pont) {
            this.vaszon.lineTo(pont.x, pont.y);
        }, this);
    }
});

vs

function kor_pontok(x, y, sugar) {  
  var lepeskoz = 2 * Math.PI / 20;  
  var pontok = [], px, py, theta;  
  for (theta = 0; theta < 2 * Math.PI; theta += lepeskoz) {  
    px = x + sugar * Math.cos(theta);  
    py = y - sugar * Math.sin(theta);  
    pontok.push([px, py]);  
  }  
  return pontok;  
}
  
function rajzol(vaszon, pontok) {  
  for (var i = 0; i < pontok.length; i++) {  
    vaszon.lineTo(pontok[i][0], pontok[i][1]);  
  }  
} 
Nálam az a konklúzió, hogy ilyen kis mennyiségű kódból nem jön le, hogy melyik az előnyösebb módszer. Mindkettővel nagyjából ugyanazt el lehet érni, és mindkettőnél szét lehet szedni a kódot absztrakciós szintekre, és jól tagolt fájlokra.

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:

    var vaszon = {vaszon: "vaszon"}; 
    var fajl = {fajl: "c:/..."}
    var kor = kor_pontok({x: 100, y: 100, sugar: 200});    
    kikuld([vaszon, fajl], kor);    
Így nem kéne string-eket használni, ami nagyon error prone, mert elgépeled, és nem kéne a canvas element-et globális változóba tenni és úgy beinjektálni, meg a körnél is ránézésre látni lehetne, hogy melyik szám mit jelent. Nyilván ugyanúgy megmaradna a sok if-else, ami csak objektumokkal tűnik el, de ez velejárója annak, ha mindent struktúráltan közelítesz meg. Ha nagyon sok if-elseif-else van a kódodban, akkor el lehet gondolkodni azon, hogy érdemesebb lenne egy interface-t definiálni, és az egyes if blokkok tartalmát kiemelni külön osztályokba. Könnyebben átlátható kód lesz a vége. Nagyjából ennyi, amit az OO a struktúrálton fölül nyújt. Amit szemléletben ad, annak a nagy része ugyanúgy alkalmazható struktúrált kódnál is.
58

Köszönöm a választ, te

Hidvégi Gábor · 2015. Júl. 17. (P), 12.48
Köszönöm a választ, te legalább gondolkodtál és mérlegeltél, nem pedig csípőből tüzeltél. Mint látszik, nem lehet rámondani egyikre sem egyértelműen, hogy jobb vagy rosszabb lenne a másiknál.

A struktúrált módszernél a függőségek beinjektálását nem lehet annyira hatékonyan megoldani
Ha a dependency injectiont érted itt az injektáláson, akkor arra a strukturált módszernél nincs szükség, ha elemi függvényeket használsz, amelyek paraméterekkel dolgoznak.
60

Pontosan ez a probléma, hogy

inf3rno · 2015. Júl. 17. (P), 13.21
Pontosan ez a probléma, hogy paraméterekkel lehet csak beinjektálni eggyel felsőbb szintről, vagy globális változókkal. Egyik sem kellemes. Ha több szint van, akkor felgyűlnek a beinjektálandó paraméterek egy nagyon nagy config-ban, vagy global-t használsz, és nehéz nyomonkövetni, hogy mi hol kap értéket. OO-nál a példányosítás ki van szervezve container-ekbe, ami jobb átláthatóságot eredményez, mert flat, szemben azzal, ha egy objektum fát csinálsz úgy, hogy az objektumok egymást példányosítják. Persze lehet, hogy van valami megoldás struktúráltan is, de sosem fejlesztettem nagy dolgokat abban a stílusban. Esetleg tudnál példát mutatni arra, hogy hogyan lehet struktúráltan hosszú hívási láncon keresztül értékeket eljuttatni sok szint mélységbe hatékonyan?
61

Legfeljebb két-háromszintes

Hidvégi Gábor · 2015. Júl. 17. (P), 13.34
Legfeljebb két-háromszintes programokat írtam eddig, szóval eddig még nem okozott gondot.
65

DIC

Hidvégi Gábor · 2015. Júl. 19. (V), 10.56
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.

Pontosan ez a probléma, hogy paraméterekkel lehet csak beinjektálni eggyel felsőbb szintről, vagy globális változókkal.


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?
66

A globális névtérhez bármely

BlaZe · 2015. Júl. 19. (V), 14.42
A globális névtérhez bármely objektumnak kontrollálatlan hozzáférése van, ezért nem célszerű abból végezni a DI-t. A DIC ezzel szemben egy kontrollált hozzáférésű storage, amihez tipikusan a DI framework fér hozzá, és ő végzi az egyes objektumok közti kapcsolat felépítését, futásidőben. Tehát annak az objektumnak sincs hozzáférése, aki az adott DIC-ből kapja a függőségeit.

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.
68

Kontroll

Hidvégi Gábor · 2015. Júl. 19. (V), 15.55
Csak azért kérdem, mert Ádám és Bence azt állítják, hogy az adatrejtés az pusztán csak dokumentációs célokat szolgál, azaz egy DIC objektumot, legyen az akár egy DI keretrendszer privát tagja, csak az nem módosít, aki nem akar, kontrollálatlanul. Ha ez így van, akkor egy DIC is ugyanúgy Mindenki Macája, mint egy globális változó.
69

az adatrejtés az pusztán csak

inf3rno · 2015. Júl. 19. (V), 20.35
az adatrejtés az pusztán csak dokumentációs célokat szolgál


Azt elfelejtetted hozzátenni, hogy javascriptben, mert ők azt állítják.
71

Egyrészt javascriptes témában

Hidvégi Gábor · 2015. Júl. 19. (V), 20.45
Egyrészt javascriptes témában vagyunk, másrészt, ahol nálad a forrás (pl. php), ott sincs védelem.
72

Egyrészt PHP-ban van védelem,

inf3rno · 2015. Júl. 19. (V), 20.49
Egyrészt PHP-ban van védelem, másrészt szándékosan ferdíted el, amit ők állítanak, ami elég szánalmas.
73

Hm?

Hidvégi Gábor · 2015. Júl. 19. (V), 20.53
Pontosan milyen védelem van ott, ahol nálad a forrás, és mit ferdítek el?
78

PPP. Persze reflection-nel

inf3rno · 2015. Júl. 20. (H), 00.43
Azt ferdíted el, hogy legalább másodszor írod le, hogy

Ádám és Bence azt állítják, hogy az adatrejtés az pusztán csak dokumentációs célokat szolgál


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.
80

Ha biztonsági célokat

MadBence · 2015. Júl. 20. (H), 09.38
Ha biztonsági célokat szolgál, akkor a Reflection egy biztonsági rés lenne, és javítanák. Mivel ez nincs így, így feltételezem, hogy NEM biztonsági célokat szolgál. A láthatóság a fejlesztőnek szóló dokumentáció, amely szabályokat a fordító is kikényszeríti, ha nyelvi szinten támogatott a láthatóság koncepciója.
74

Az ellen nem véd, deviszont...

Hidvégi Gábor · 2015. Júl. 19. (V), 21.12
<?php
  class Osztály {
    protected $privát = 'privát';

    function getPrivát() {
      return $this->privát;
    }
  }

  $objektum = new Osztály;
  $reflectionClass = new ReflectionClass('Osztály');
  $reflectionProperty = $reflectionClass->getProperty('privát');
  $reflectionProperty->setAccessible(true);
  $reflectionProperty->setValue($objektum, 'nem annyira privát');
  print $objektum->getPrivát();
?>
76

A "nem lehet", az arról

pp · 2015. Júl. 19. (V), 22.16
A "nem lehet", az arról szólt, hogy "ahhoz hogy módosítsd nagyon dolgoznod kell", tehát közben fel kellene merülnie benned, hogy valamiért nem akarják, hogy módosítsd, keress valami jobb megoldást.

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
82

Függőség

Hidvégi Gábor · 2015. Júl. 20. (H), 13.46
Nem is az a lényeg, hogy mit lehet és mit nem, hanem az, hogy van egy szerződés, miszerint bizonyos objektumokat csak bizonyos módon lehet elérni. Írni-olvasni minden esetben lehet őket, bár a DI keretrendszer ezt megnehezíti, de nem akadályozza meg.

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.
83

Persze, hogy meg lehet ezt

pp · 2015. Júl. 20. (H), 14.39
Persze, hogy meg lehet ezt tenni, de miért tegyük, ha van hozzá egy eszköz(OOP nyelvi elemek), amit használhatunk?

pp
84

Melyik nyelvi elemekre

Hidvégi Gábor · 2015. Júl. 20. (H), 15.13
Melyik nyelvi elemekre célzol?
85

Szerinted? pp

pp · 2015. Júl. 20. (H), 16.10
86

Nem értem

Hidvégi Gábor · 2015. Júl. 20. (H), 17.49
Sajnos nem értem, mire célzol. Kifejtenéd, miben segítik az OOP eszközei a fent vázolt probléma megoldását?
87

Nem értem, "fent"-i alatt mit

pp · 2015. Júl. 20. (H), 18.33
Nem értem, "fent"-i alatt mit értesz? Lehet elbeszélünk egymás mellett? :)

É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.
88

Uncsi már ez

bamegakapa · 2015. Júl. 20. (H), 18.36
Ennyi erővel meg lehetne tenni ezt is. De minek, ha már az egész szépen ki van találva, és nem kell folyton minden apróságról megegyezni a kollégákkal, hanem egyszerűen mindenki végzi a munkáját, mert mind értetek az OOP-hez? Minek barkács megoldásokkal összerakni olyat, amit mások kemény munkával profi módon már megépítettek, amikor a két dolog ugyanazt valósítja meg?

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.
91

+1. Szerintem struktúrált

inf3rno · 2015. Júl. 20. (H), 22.44
+1. Szerintem struktúrált programozáshoz is biztosan van egy ajánlott szabály gyűjtemény, amit jobb helyeken alkalmaznak, csak nem hallani annyit róla, mert általában mi oop-ben dolgozunk (én legalábbis).
92

Gábor eddig nem említett

bamegakapa · 2015. Júl. 20. (H), 23.52
Gábor eddig nem említett ilyesmit, azt hiszem. Ennek ellenére bizonyára megvannak ott is a szakkifejezések és kidolgozott alapelvek. Azon se lepődnék meg, ha erős átfedések lennének az OOP-s fekete mágiával.
96

Szerintem jobb agyalni egy

inf3rno · 2015. Júl. 21. (K), 02.03
Szerintem jobb agyalni egy kicsit a témán, nem mindig csak Gáborral vitatkozni. Nem biztos, hogy ő a megfelelő forrás ezen a téren, vagy rossz kérdéseket teszünk fel. :D

Ha belegondolsz kb ennyi oo osztályokat adatstruktúrákra és függvényekre átfordítani:

oo

var builder = new YourMapBuilder(new MyMapFactory);
builder.createMap();
builder.addA(13);
builder.addB();
builder.removeA();
var map = builder.getMap();

var YourMapBuilder = Object.extend({
	constructor: function (factory){
		this.factory = factory;
	},
	createMap: function (){
		this.map = factory.createMap();
	},
	addA: function (value){
		this.map.put("a", value || 1);
	},
	addB: function (){
		this.map.put("b", 2);
	},
	removeA: function (){
		this.map.remove("a");
	},
	getMap: function (){
		return this.map;
	}
});

var MyMapFactory = Object.extend({
	createMap: function (){
		return new MyMap();
	}
});

var MyMap = Object.extend({
	constructor: function (){
		this.data = {};
	},
	contains: function (key){
		return (key in this.data);
	},
	put: function (key, value){
		this.data[key] = value;
	},
	remove: function (key){
		delete(this.data[key]);
	},
	get: function (key){
		return this.data[key];
	}
});
struktúrált + dependency inversion

var builder = yourMapBuilder;
var data = builder.createMap(myMapFactory.createMap);
builder.addA(myMap, data, 13);
builder.addB(myMap, data);
builder.removeA(myMap, data);

var yourMapBuilder = {
	createMap: function (factory){
		return factory();
	},
	addA: function (utils, data, value){
		utils.put(data, "a", value || 1);
	},
	addB: function (utils, data){
		utils.put(data, "b", 2);
	},
	removeA: function (utils, data){
		utils.remove(data, "a");
	}
};

var myMapFactory = {
	createMap: function (){
		return {};
	}
};

var myMap = {
	contains: function (data, key){
		return (key in data);
	},
	put: function (data, key, value){
		data[key] = value;
	},
	remove: function (data, key){
		delete(data[key]);
	},
	get: function (data, key){
		return data[key];
	}
};
Nagyjából ugyanazokat a mintákat alkalmazni lehet, mert fapadosan bár, de ugyanúgy megvalósítható a dependency inversion. Az öröklődés fapadosan szintén megoldható egy for-in-es függvény másolással. Js nagyon rugalmas, valszeg más nyelveken azért nem ennyire egyszerű a helyzet, meg szórakozhatunk string-ként megadott függvény nevekkel és névterekkel, stb... Úgyhogy elég kényelmetlen lehet a dolog.

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

var builder = yourMapBuilder;
var data = builder.createMap();
builder.addA(data, 13);
builder.addB(data);
builder.removeA(data);

var yourMapBuilder = {
	createMap: function (){
		return myMapFactory.createMap();
	},
	addA: function (data, value){
		myMap.put(data, "a", value || 1);
	},
	addB: function (data){
		myMap.put(data, "b", 2);
	},
	removeA: function (data){
		myMap.remove(data, "a");
	}
};

var myMapFactory = {
	createMap: function (){
		return {};
	}
};

var myMap = {
	contains: function (data, key){
		return (key in data);
	},
	put: function (data, key, value){
		data[key] = value;
	},
	remove: function (data, key){
		delete(data[key]);
	},
	get: function (data, key){
		return data[key];
	}
};
Szóval az OO annyival nyújt többet, hogy a data és a utils összekötve utazik egy változóban úgy, hogy az adat rejtve marad (ppp) és nem kell paraméterben átadni, mert context-ben megkapják a metódusok. A másik extra, hogy az olyan dolgokat, mint dependency inversion, öröklődés, stb... megtámogatja a nyelv pl extends-el, interface-ekkel, és hasonló nyelvi elemekkel, amik ugye szabványosak. Mivel szabványosak, ezért az IDE-k is megtámogatják őket, és ezért sokkal egyszerűbb az élet OO-val, mint struktúráltan, ha lazán csatolt kódot fejleszt az ember. A szorosan csatolt kód meg érthetően nem mindenhol válik be.
97

Jaja, ezért mondom, hogy

bamegakapa · 2015. Júl. 21. (K), 08.12
Jaja, ezért mondom, hogy erősek az átfedések, ami az elveket illeti. Most, hogy erre ismét ráébredtünk, pár nap múlva újrakezdhetjük valamelyik random topikban...
98

Azt mondják, hogyha valamit

inf3rno · 2015. Júl. 21. (K), 16.18
Azt mondják, hogyha valamit 17x megtanulsz, akkor utána már nem tudod elfelejteni. Hát nem vagyok benne biztos, hogy ez mindenkire érvényes...
75

Nem cél, hogy a programozótól

BlaZe · 2015. Júl. 19. (V), 22.08
Nem cél, hogy a programozótól védjük meg a programot, amit ír. Ahogy leírták többen is, ez nem biztonsági védelem.

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.
49

Nem erőltetném a dolgot, ha

Joó Ádám · 2015. Júl. 16. (Cs), 21.30
Nem erőltetném a dolgot, ha valóban kielégítő válaszokat kaptam volna, és nem lennének nyilvánvaló ellentmondások a témában, mint például ebben a cikkben, amikre nem csak én mutatok rá, hanem millió írást lehet találni rájuk a neten.


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.
50

A neten pedig arról is

bamegakapa · 2015. Júl. 16. (Cs), 21.54
A neten pedig arról is rengeteg írást lehet találni, hogy a Föld ötezer éves.

Nice :).
47

Látod, ezért kérdeztem, hogy

Joó Ádám · 2015. Júl. 16. (Cs), 21.12
Látod, ezért kérdeztem, hogy mikor érdemes használni öröklődést. Azt írod, hogy gyakran, amiből arra következtetek, hogy legalább az esetek 50%-ában. Mik azok a használati esetek, ahol nem okoz gondot?


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.

És ez mikor jó? A területszámítós példámban nincs szükség ilyenre, bejön a paraméter, abból kiderítjük, hogy milyen a típusa, és a megfelelő ágban kiszámoljuk a területét.


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.

Szerintem meg pont hogy mindkettő hívás procedurális.


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.

Javascriptben minden metódus és adattag egy darab objektumhoz tartozhat


Akkor lehet, hogy nem érted a nyelvet:
var f = function () {
    alert(this);
}

var a = {};
var b = {};

a.f = f;
b.f = f;
Egyébként a legtöbbet az Array.prototype.slice.call(arguments)-et lehet látni, ami azt mutatja meg, hogy rosszul lett megtervezve az arguments 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?


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!

Ha az adatok és függvényeik össze vannak házasítva, akkor erős köztük a köté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 két mondatod között ellentét feszül. Akkor mégiscsak meg kell védeni őket, mert különben hibás működés léphet fel, nem?


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?
55

Az öröklődés szintaktikai

Hidvégi Gábor · 2015. Júl. 17. (P), 09.34
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.
Erre írtam valahol korábban, hogy nagy jóstehetség kell ahhoz, hogy egy adott funkcióra minden gyermekben szükség lesz.

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.

»Javascriptben minden metódus és adattag egy darab objektumhoz tartozhat«

Akkor lehet, hogy nem érted a nyelvet:
var f = function () {
    alert(this);
}

var a = {};
var b = {};

a.f = f;
b.f = f;
Ez ugyanolyan, hogy az ember nem tud repülni (legfeljebb zuhanni). Ha felülsz egy repülőre, akkor téged repültetnek, de magadtól nem vagy képes a levegőben huzamosabb ideig előre haladni.

A fenti példádban az a.f-ben az f csak egy mutató az eredeti f függvényre, ami a window-ban csücsül. Ha meg szeretnéd az f-et változtatni, akkor ehhez az egyhez kell visszanyúlnod, mert ha az a.f-et írod át, akkor csak az a metódusa lesz más.

Tehát akkor én nem ismerem a nyelvet, vagy pedig te érveltél átgondolatlanul?

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á.
Igen, és tegyünk még bele privát változókat, és onnantól kezdve nem tudhatom, hogy egy metódust azonos paraméterekkel meghívva kétszer ugyanazt az eredményt kapom-e, anélkül, hogy átnézném a kódot. Ez sérti az egységbezárás elvét, azaz az objektumot nem használhatom fekete dobozként.

»Ha az adatok és függvényeik össze vannak házasítva, akkor erős köztük a köté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.
Erős cinizmust érzek ki a soraidból, ha így van, kérlek, a jövőben kímélj meg ettől. Én valóban szeretnék választ kapni a kérdéseimre.
56

Példa: a bankban bankszámlán

MadBence · 2015. Júl. 17. (P), 11.21
Példa: a bankban bankszámlán tartjuk a pénzünket.
Attól, hogy mutatsz egy nyilvánvalóan rossz példát az öröklésre, még nem lesz maga a koncepció hibás (csak megmutatod, hogy nem megoldás mindenre, amit egyébként szerintem senki sem vitatott).
Ezen korlátok definícióját szeretném megkapni.
Ahol is-a viszonyban van két típus, ott lehet gyanítani, hogy szükség lehet rá (ha valami konkrétabb definíciót szeretnél: Liskov substitution principle). Nyelvi akadályok miatt ez nem feltétlenül megoldható minden esetben.
A fenti példádban az a.f-ben az f csak egy mutató az eredeti f függvényre, ami a window-ban csücsül. Ha meg szeretnéd az f-et változtatni, akkor ehhez az egyhez kell visszanyúlnod, mert ha az a.f-et írod át, akkor csak az a metódusa lesz más.
Tehát a valódi referenciákat hiányolod? Akkor a java és a C# is hasonlóan koncepcionálisan hibás nyelv lenne?
Igen, és tegyünk még bele privát változókat, és onnantól kezdve nem tudhatom, hogy egy metódust azonos paraméterekkel meghívva kétszer ugyanazt az eredményt kapom-e, anélkül, hogy átnézném a kódot.
Akkor ne használj adatbázist se, mert kétszer lefuttatva ugyanazt a lekérdezést más eredményt kaphatsz!
Erős cinizmust érzek ki a soraidból, ha így van, kérlek, a jövőben kímélj meg ettől. Én valóban szeretnék választ kapni a kérdéseimre.
http://stackoverflow.com/questions/14000762/what-does-low-in-coupling-and-high-in-cohesion-mean
57

Az első pár kérdésedre

MadBence · 2015. Júl. 17. (P), 11.44
Az első pár kérdésedre választ az alábbi linkeken találhatsz:
Sajnos nem találtam, ezek mind a többszörös öröklés maceráira példák, amire egy általánosan működő megoldás a Composition over Inheritance. Továbbra is állítom, hogy ha egy függvényt megváltoztatsz (vö. egy metódust megváltoztatsz az ősosztályban) minden más kódot át kell nézned, ami erre a függvényre támaszkodik (vö. minden kódot át kell nézned, ami erre a metódusra támaszkodik).
És ez mikor jó? A területszámítós példámban nincs szükség ilyenre, bejön a paraméter, abból kiderítjük, hogy milyen a típusa, és a megfelelő ágban kiszámoljuk a területét.
Azért jó a dynamic dispatch, mert az ágakat nem neked kell megírni, a megfelelő implementáció automatikusan meghívódik.
Szerintem meg pont hogy mindkettő hívás procedurális.
Én ezzel nem tudok mit kezdeni, nem győztél meg :).
A két mondatod között ellentét feszül. Akkor mégiscsak meg kell védeni őket, mert különben hibás működés léphet fel, nem?
Igen, a fordító megvéd, hisz nem fordul le a kód. Mivel JS-ben nincs ilyen, így máshogy kell ezt megoldani (prefix, annotáció). Fizikai védelmet nem nyújt a privát láthatóság (egy lefordított binárisban nem is fogsz semmilyen védelmet találni, a láthatósági beállítások csak és kizárólag a fordítóknak szólnak).
Hiding the internals of the object protects its integrity by preventing users from setting the internal data of the component into an invalid or inconsistent state. A supposed benefit of encapsulation is that it can reduce system complexity, and thus increase robustness, by allowing the developer to limit the inter-dependencies between software components.


objektum.metodus(parameter);

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.
19

Refaktoring könyv példája

T.G · 2015. Júl. 16. (Cs), 07.35
Hol érdemes öröklődést használni?

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.
21

Túl egyszerű?

Hidvégi Gábor · 2015. Júl. 16. (Cs), 09.42
Olvastad a linkelt oldalon lévő tanulmányt? Szerinted az Móriczka-példákon keresztül bizonyítja be, hogy a Demeter törvénye alapján az öröklődés alapjaiban hibás elképzelés?
40

Nem olvastam...

T.G · 2015. Júl. 16. (Cs), 15.39
Nem olvastam a szóban forgó irományt, de tényleg 600 oldalon keresztül kellene bizonygatni, hogy alapjaiban hibás az öröklődés? Ennél én egy kevésbé hosszú, olvasmányos leírást ajánlottam, amely ennek ellenkezőjét bizonyítja be.
44

Én régebbi extjs-ben láttam 6

inf3rno · 2015. Júl. 16. (Cs), 18.30
Én régebbi extjs-ben láttam 6 szintes öröklődést. Nem tetszett. Szerintem a composition jobb megoldás lett volna. Nem tudom az újabb extjs változatokkal mi a helyzet. Persze az megint más kérdés, hogy most a struktúrált vs oo vitáról vagy a composition vs inheritance vitáról van szó, mert Gábor próbál erősen megvezetni minket ezen a téren...
46

mixin

T.G · 2015. Júl. 16. (Cs), 20.52
A négyes verzióban bevezették a Mixin-eket, amelyek segítségével (kisebb-nagyobb sikerrel:) a hosszabb öröklési láncokat igyekeztek csökkenteni. Én használok ExtJS-t és elégedett vagyok vele, ám biztos vagyok benne, hogy egy ekkora nagy kódbázisban, amit egyszerre jónéhány ember fejleszt, nem minden optimális. Ezt nem is vita. :)

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.
51

A négyes verzióban bevezették

inf3rno · 2015. Júl. 16. (Cs), 22.04
A négyes verzióban bevezették a Mixin-eket, amelyek segítségével (kisebb-nagyobb sikerrel:) a hosszabb öröklési láncokat igyekeztek csökkenteni.


Jaja, ez még az extjs3 volt. Azt mondják a 4 tényleg sokkal jobb meg gyorsabb lett.

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.


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.
48

Szerintem a composition jobb

Joó Ádám · 2015. Júl. 16. (Cs), 21.29
Szerintem a composition jobb megoldás lett volna.


Miért? Ha valóban is-a a viszony a két osztály között, akkor csak többet gépelsz vele.
52

Egyszerűen az volt a

inf3rno · 2015. Júl. 16. (Cs), 22.07
Egyszerűen az volt a véleményem amikor néztem az ext3 kódját évekkel ezelőtt. Lehet, hogy te más véleményen lettél volna, nézd meg te is, aztán meglátjuk.
53

Ha van kedved megkeresni a

Joó Ádám · 2015. Júl. 16. (Cs), 23.40
Ha van kedved megkeresni a konkrét példát, akkor szívesen megnézem. Én úgy érzem, hogy szerencsétlen öröklésnek rosszabb a megítélése, mint amit megérdemel.

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.
59

Innen le lehet rántani

inf3rno · 2015. Júl. 17. (P), 13.12
Innen le lehet rántani link.

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:

Ext.tree.TreePanel = Ext.extend(Ext.Panel, {});
Ext.Panel = Ext.extend(Ext.Container, {});
Ext.Container = Ext.extend(Ext.BoxComponent, {});
Ext.BoxComponent = Ext.extend(Ext.Component, {});
Ext.Component = function(config){};

Ext.tree.TreeNode = Ext.extend(Ext.data.Node, {});
Ext.data.Node = Ext.extend(Ext.util.Observable, {});
EXTUTIL.Observable = function(){};
Nekem egy kicsit sok volt az 5 szint mélységű öröklődés a TreePanel esetében. Nehéz volt kikeresni a kódból, hogy mi mit csinál, és melyik osztályban, a dokumentációban meg nem volt benne minden. Egyébként nem lett volna szükségem a kód böngészésére, ha a TreePanel és a TreeNode nincs szoros csatolásban egymással, és le tudtam volna cserélni az implementációjukat. Sajnos tervezési hibásak voltak. Nem tudom, hogy a mostani ext-nél javították e már ezt. Arra sem emlékszem, hogy csináltam e issue-t ezzel kapcsolatban, valami olyasmi rémlik, hogy széttárták a karjukat azzal, hogy hát nem kiterjeszthető a TreePanel, és viszlát.
45

Ha például az alapobjektumon

Joó Ádám · 2015. Júl. 16. (Cs), 20.25
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.


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.

Ha az öröklődés ennyi problémával jár, az ES6-ban miért vezették be a class-t és az extends-et?


A gyakran ismétlődő szintaktikai mintákat szokás beépített konstrukciókkal támogatni.

Ha az öröklődés kiesik, mi az értelme az objektumoknak? Ha nincs öröklődés, akkor egy objektum.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?


Az objektum polimorf argumentum.

Miért kell az adatokat és a függvényeket összeházasítani?


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.

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.


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.

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.


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.

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.


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.

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


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.

A fentiek után felmerülhet a kérdés, hogy van-e egyáltalán OOP a javascriptben?


Lássuk csak, vannak összetett adattípusok, van polimorfizmus: igen, van OOP a JavaScriptben.
54

Ahogy Bence is írta, ez

Hidvégi Gábor · 2015. Júl. 17. (P), 07.02
Ahogy Bence is írta, ez minden interfészre igaz, és erre nem reagáltál érdemben.
Reagáltam, ott van a három link a hozzászólás elején, arra pedig sem ő, sem te nem reagáltatok. Az valóban nem egyértelmű, hogy amikor a fenti mondatot leírtam, a tipikus is-a has-a problémára gondoltam, amik rögtön előjönnek öröklődés esetén, hisz a gyermekosztályok statikusan kapcsolódnak a szüleikhez. Példa: autó. Vannak kerekei, kormánya. Amikor kormányzom, az első két kerék fordul. Később kitalálja a megrendelő, hogy légpárnás járművet is szeretne, ekkor írhatom át az egész ősosztályt, mert bár kormányozható, de nincsenek kerekei, hanem a kormánylapátot kell mozgatni.

Az objektum polimorf argumentum.
És az miért jobb annál, mintha függvényeket használnánk?

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.


»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.«

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.
Erre ugyancsak hoztam példát a fentebb linkelt hozzászólásban (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?

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
Akkor miért lehet sok helyen arról olvasni, hogy ezek biztonsági dolgok? Kinek van igaza? Jó lenne tudni, mert nagyon nem mindegy, hogy igen vagy nem.

az öröklődés nem arra való, hogy megkerüld az interfészt, hanem hogy bővítsd
És ha a privát metódus egy fontos dolgot csinál, de mondjuk alapvetően hibás?


Amire nem reagálok, azt elfogadom.
63

Egységbezárás

Hidvégi Gábor · 2015. Júl. 18. (Szo), 09.19
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
Ennek pont az ellenkezőjét állítja Felhő a korábbi cikkében:
Egy modul készítése esetén nem tudjuk elérni, hogy annak felhasználója esetleg nem megfelelő módon alkalmazza, ami inkonzisztens állapothoz vezethet. Egy objektum esetén (a PHP 5-ös verziójától kezdve) szabályozni tudjuk a benne lévő változók és metódusok láthatóságát, biztosítani tudjuk, hogy a programozó az objektumot csak annak felületén keresztül tudja felhasználni.
Valamelyikőtök téved. Jó lenne, ha megegyeznétek, mert ez így nagyon félrevezető.
64

Felhő is ugyanezt állítja.

MadBence · 2015. Júl. 18. (Szo), 11.37
Felhő is ugyanezt állítja. Biztosítani tudjuk, hogy az objektumokat csak a publikus interfészén keresztül lehet módosítani (PHP esetén ez nyelvi szinten kényszeríthető ki, JS esetén dokumentácós szinten).
5

Öröklődés

Hidvégi Gábor · 2015. Júl. 15. (Sze), 13.33
Továbbiak jutottak eszembe az öröklődéssel kapcsolatban:

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:

function terulet(alakzat) {
  if (alakzat.tipus === 'negyzet') {
    return alakzat.szelesseg * alakzat.szelesseg;
  }
  if (alakzat.tipus === 'teglalap') {
    return alakzat.szelesseg * alakzat.magassag;
  }
}
10

Jól látod :)

pp · 2015. Júl. 15. (Sze), 16.06
Tényleg gáz példákat hoznak. Az oop azért jó, mert olyan absztrakciókat tudok benne leírni, amit más eszközökkel én nem olyan könnyedén tudnék megtenni.

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
11

Öröklődés

Hidvégi Gábor · 2015. Júl. 15. (Sze), 16.54
Örülök, hogy egy tanárember is van itt, mert akkor biztos megalapozottan tudsz válaszolni a fenti kérdéseimre.

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?

Úgy gondolom, hogy az OOP-s eszközökhöz, lévén szabványosak sokkal könnyebb IDE támogatást fejleszteni
Ez tipikusan a tyúk és a tojás esete: miért kell integrált fejlesztői eszköz, ha primitívekkel dolgozol?

Persze mindez a JavaScript témakörön belül nem biztos, hogy releváns
A téma címe Objektumorentált programozás javascriptben. Egyébként miért van az, hogy ha megnézed a frontend álláshirdetéseket, sehol sem kérnek OOP-t?
13

Már jóideje programozok

inf3rno · 2015. Júl. 15. (Sze), 17.25
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?


Nagyon egyszerű, mert mindent meg lehet oldani struktúráltan OOP nélkül is, csak nem biztos, hogy érdemes.
22

Kérdés

Hidvégi Gábor · 2015. Júl. 16. (Cs), 09.46
Évek óta azt kérdem, és még nem kaptam rá érdemleges választ: mikor érdemes OOP-t választani? Miről lehet felismerni a programban azt a mintázatot, hogy "igen, innentől kezdve procedurálisan szenvedés, objektumokkal kell dolgozni"?
31

Kérdés

Gixx · 2015. Júl. 16. (Cs), 14.34
Évek óta azt kérdem, és még nem kaptam rá érdemleges választ: mikor érdemes procedurális programozást választani? Miről lehet felismerni a programban azt a mintázatot, hogy "igen, innentől kezdve objektum orientáltan szenvedés, függvényekkel és eljárásokkal kell dolgozni"?
33

Válasz

Hidvégi Gábor · 2015. Júl. 16. (Cs), 14.43
Alapból procedurális, mivel jóval egyszerűbb, de ha megvan a minta, ahol már érdemes áttérni OOP-re, onnantól OOP.
41

Pontosan

Gixx · 2015. Júl. 16. (Cs), 16.06
Pontosan ez az. Két külön szemlélet. Egyik sem előbbrevaló a másiknál, mindenki azt használja (jó esetben), amelyik számára természetesebben hat.

É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:

- Minek költöztél egy szigetre, ha félsz a víztől?
- Végülis, csak a tenger felől nézve sziget.


// nem szó szerinti idézet
35

Alapból OOP, mivel jóval

pp · 2015. Júl. 16. (Cs), 14.49
Alapból OOP, mivel jóval egyszerűbb, de ha megvan a minta, ahol már érdemes áttérni procedurálisra, onnantól procedurális.
14

"Pontosan mit mondasz el a

pp · 2015. Júl. 15. (Sze), 17.43
"Pontosan mit mondasz el a nebulóidnak"

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.)
23

picit zavar a nebuló

Hidvégi Gábor · 2015. Júl. 16. (Cs), 10.01
picit zavar a nebuló kifejezés, mert egyfajta degradáló jelzőnek gondolom
Semmi ilyen célom nem volt, valamiért azt hittem, hogy középiskolában tanítasz. De ennek egyébként nincs semmilyen jelentősége, képzelj a helyére nyugodtan bármilyen más, megfelelő szót.

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.
$bejovo_adatok = array(
  'email' => array(
    array('kotelezo'  => 1),
    array('formatum'  => 'email'),
    array('adatbazis' => 'nem_regisztralt'),
  ),
  'nev'   => array(
    array('formatum' => 'regexp', 'regexp' = '//')
  ),
);
$eredmeny = urlap_ellenoriz($bejovo_adatok);


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
Ezt nem értem, kérlek, fejtsd ki!
24

Tök jó példa. Ha jól értem,

pp · 2015. Júl. 16. (Cs), 10.44
Tök jó példa.

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.


Ezt nem értem, kérlek, fejtsd ki!


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
25

Újra?

Hidvégi Gábor · 2015. Júl. 16. (Cs), 11.06
Miért implementálnék újra bármit? Az ellenőrző függvényt egyszer kellett megírnom, a bejövő paraméterek meg űrlaponként változnak. Természetesen vannak közös pontok, például vegyük a regisztrációt és a bejelentkezést, mindkettőnél kell ellenőrizni az emailcímet, ami két részből áll:
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:

$global_email_ell = array(
  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.

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.
Hol írom ezt, mit húz alá, és honnan tudja, hol van a hiba?
28

Ez jó példa arra, hogy mit

pp · 2015. Júl. 16. (Cs), 14.21
Ez jó példa arra, hogy mit nem kéne összefogni, mert ugye nem minden mail cím kötelező.

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
32

Nem írtam sehol, hogy

Hidvégi Gábor · 2015. Júl. 16. (Cs), 14.41
Nem írtam sehol, hogy kötelezően használok összefogást. Hoztam két példát: a regisztrációt és a bejelentkezést, itt mindkét esetben kötelező megadni, ezért az ismétlődést kitettem egy változóba. Értelemszerűen ott, ahol nem kötelező egy emailcím megadása, ott nem fogom a változót használni.

Sajnálom, hogy nem tudom, számodra is érthetően megfogalmazni azt amit mondani akarok
Azért megpróbálhatnád. A tanítványaidnak hogyan adod át? Ha valamit nem lehet egyszerűen elmagyarázni, akkor nem gyanús, hogy ott valami gond van?

és félek itt a fórum keretei között nem is leszek rá képes
Engem nem zavar, ha üvöltve írsz : )
34

Azért

pp · 2015. Júl. 16. (Cs), 14.48
Azért megpróbálhatnád.


Sajnálom, ha úgy érzed, hogy meg se próbáltam.

Ha valamit nem lehet egyszerűen elmagyarázni, akkor nem gyanús, hogy ott valami gond van?


Arra gondolsz, hogy a Maxwell egyenletek úgy ahogy vannak értelmetlen hülyeségek, puszta úri huncutság?

pp
62

Az IDE meg nem csak hogy

Hidvégi Gábor · 2015. Júl. 18. (Szo), 08.42
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.
Ezt a mondatodat figyelmetlenül olvastam, azért nem értettem.
12

Nagyon jó a példád. Nyilván a

inf3rno · 2015. Júl. 15. (Sze), 17.21
Nagyon jó a példád. Nyilván a négyzet leszármazottja a téglalapnak, ahogy matekból is tanultuk. Annyi extra van, hogy felül kell írni a konstruktort, és csak egy oldalhossz értéket bekérni. Már ha constructor injection-el kerül be a nagyság.

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.
99

Absztrakció

Hidvégi Gábor · 2015. Júl. 22. (Sze), 10.14
Már csak az a kérdés, hogy kinek való az OOP. A sokszor említett absztrakció lényege, hogy bizonyos problémákat leegyszerűsítünk úgy, hogy feltételezésekkel élünk (ez és ez így fog történni), magyarul információt veszítünk. Ezáltal azt nyerjük, hogy a program rövidebb és egyszerűbb lesz, de amint kiderül, hogy rosszul mértük fel a helyzetet, baj van, és ez már az ebben a témában felhozott Móriczka-példáknál is előjött.

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

//fő program
if (feltetel) {
  tedd_ezt();
}
else {
  tedd_emezt();
}
OOP:
if (feltetel) {
  var objektum = new Ezt_tevo_osztaly;
else {
  var objektum = new Emezt_tevo_osztaly;
}

//fő program
objektum.tedd();
A fő program valóban jóval egyszerűbb és olvashatóbb, mint procedurálisan, hisz nem kell azzal foglalkoznom, hogy milyen módon állítom elő az objektumot. Az objektum példányosítása nem a fő programban van.

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.
//fő program
if (feltetel) {
  var tovabbi_feltetel = vizsgalat_eredmenye();
  if (tovabbi_feltetel) {
    tedd_ezt_így();
  }
  else {
    tedd_amazt();
  }
}
else {
  tedd_emezt();
}
OOP-ben – kapa ajánlásra – ezt egy Gyár Mintával lehet a legegyszerűbben megoldani:

function Objektomgyar() {}
Objektomgyar.prototype.gyart = function objektom_gyart(feltetel, eredmeny) {
  if (feltetel) {
    return eredmeny ? new Ezt_tevo_osztaly : new Amazt_tevo_osztaly;
  else {
    return new Emezt_tevo_osztaly;
  }
}

//fő program
var gyar = new Objektomgyar;
var objektum = gyar.gyart(feltetel, vizsgalat_eredmenye());
objektum.tedd();
Mint látható, az objektumok példányosítását kénytelenek voltunk áttenni a program másik részébe.

V3

Végül azt kérte az ügyfél, hogy a tedd_amazt függvény akkor is fusson le, ha nem teljesül a feltétel.
//fő program
if (feltetel) {
  var tovabbi_feltetel = vizsgalat_eredmenye();
  if (tovabbi_feltetel) {
    tedd_ezt_így();
  }
  else {
    tedd_amazt();
  }
}
else {
  tedd_emezt();
  tedd_amazt();
}
OOP-ben ekkor – Blaze ajánlására – a kompozit mintát fogjuk használni.

function Konpozit() {
  this.objektumok = [];
}
Konpozit.prototype.betesz = function konpozitba_tesz(objektum) {
  this.objektumok.push(objektum);
}
Konpozit.prototype.futtat = function konpozit_futtat() {
  for (var j = 0; j < this.objektumok.length(); j++) {
    this.objektumok[j].tedd();
  }
}

//fő program
var gyar = new Objektomgyar;
var konpozit = new Konpozit;

konpozit.betesz(gyar.gyart(feltetel, vizsgalat_eredmenye()));
if (!feltetel) {
  konpozit.betesz(new Amazt_tevo_osztaly);
}
konpozit.futtat();

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.
100

Kétszer kellett átírni a

MadBence · 2015. Júl. 22. (Sze), 10.46
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.
A refaktorálás nem ezt jelenti. A refaktorálás az, amikor úgy írod át a kódot, hogy az ég egy adta világon sehogy nem változik meg a viselkedése.
(pl. zsinórban CMS-eket gyártunk)
Most komolyan, ki gyárt zsinórban CMS-eket? Dunát lehet velük rekeszteni, minek minden projecthez ugyanazt huszadszor implementálni? Ilyet senki sem csinál.
Emellett a program hosszabb lett
Jó ötlet a sorok száma és a szoftver minősége között ilyen szoros kapcsolatot alkotni?
valamint komplexebb is
Definiáld kérlek a komplexitást :-). Ha a ciklomatikus komplexitást nézzük, akkor a procedurális kódod sokkal bonyolultabb.
101

Most komolyan, ki gyárt

Hidvégi Gábor · 2015. Júl. 22. (Sze), 11.29
Most komolyan, ki gyárt zsinórban CMS-eket?
Példa volt. Ha minden projekted más és más, akkor nem éri meg OOP-vel kezdeni.

Jó ötlet a sorok száma és a szoftver minősége között ilyen szoros kapcsolatot alkotni?
Az egy dolog, hogy a kód egy bizonyos része a ceremónia, az OOP "szintaktika", de a három alaposztály mellé két újat is be kellett vezetni (gyár, kompozit).

Definiáld kérlek a komplexitást :-)
A program végeredményben háromféle eljárást hív a feltételeknek megfelelően. Ahhoz, hogy az OOP szintaktikájának és az ismert mintáknak megfeleljen a kód, két új osztályt és logikai réteget kellett bevezetni. Ezáltal a kód összetettebb lett.

Ha a ciklomatikus komplexitást nézzük, akkor a procedurális kódod sokkal bonyolultabb.
Nem hiszem. Mint az előbb írtam, a három eljárást kell a feltételeknek megfelelően meghívni. A feltételeknek az OOP esetében is teljesülniük kell, annyi a különbség, hogy a dinamikus útválasztás miatt a feltételek kiértékelését a program más részére száműztük. Az OOP példában az összefüggő komponensek száma nőtt, azaz szerintem pont ebben az esetben nagyobb a ciklomatikus komplexitás.
102

"Ha a ciklomatikus

MadBence · 2015. Júl. 22. (Sze), 12.51
"Ha a ciklomatikus komplexitást nézzük, akkor a procedurális kódod sokkal bonyolultabb."
Nem hiszem.
Ez a metrika speciel elég egyszerűen számolható, és nem szubjektív. A te kódodnál 4 (mert 4 féle útvonalat járhat be a főprogram), az objektum-orientált megoldásban ez 2 (belefut a feltételbe, vagy sem).
hhoz, hogy az OOP szintaktikájának és az ismert mintáknak megfeleljen a kód, két új osztályt és logikai réteget kellett bevezetni. Ezáltal a kód összetettebb lett.
Viszont egy fejlesztő ismeri ezeket a mintákat, nem kell megértenie a működését, a viszonyát a többi komponenshez, tudja milyen problémát akartak vele megoldani. De igazad van, egy laikus számára tényleg bonyolultabbnak tűnhet a program. Nagyobb projekteknél ez hátrány masszív előnnyé változik (ami mondjuk nem egy sql lekérdezés eredményének formázott megjelenítésre).
103

Komplexitás

Hidvégi Gábor · 2015. Júl. 22. (Sze), 13.34
A te kódodnál 4 (mert 4 féle útvonalat járhat be a főprogram), az objektum-orientált megoldásban ez 2 (belefut a feltételbe, vagy sem).
Ezt a logikát követve, ha a főprogramom tartalmát beteszem egy függvénybe:
function egyszeru() {
  if (feltetel) {
    var tovabbi_feltetel = vizsgalat_eredmenye();
    if (tovabbi_feltetel) {
      tedd_ezt_így();
    }
    else {
      tedd_amazt();
    }
  }
  else {
    tedd_emezt();
    tedd_amazt();
  }
}

//fő program
egyszeru();
akkor a fő programom ciklomatikus komplexitását 1-re csökkentettem, azaz nyertem! : )

Ezek után kérdés, mi is a ciklomatikus komplexitás értelme.

Nagyobb projekteknél ez hátrány masszív előnnyé változik
Mi számít nagyobb programnak és mi az a masszív előny?
104

akkor a fő programom

MadBence · 2015. Júl. 22. (Sze), 13.47
akkor a fő programom ciklomatikus komplexitását 1-re csökkentettem, azaz nyertem! : )

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.

Mi számít nagyobb programnak és mi az a masszív előny?
Konkrétumokra ne számíts, ha lehetne ilyen határt húzni, akkor már rég gépek írnák a programokat. A masszív előnyt pedig az absztrakciók jelentik, hiszen nem kell átlátnod az egész programot implementációs szinten, pusztán azt az absztrakciós réteget, amivel éppen foglalkoznod kell.
106

Ha megnézed, az

Hidvégi Gábor · 2015. Júl. 22. (Sze), 14.49
Ha megnézed, az objektum-orientált változat csak egyszerű függvényekből építkezik.
Igen, de ha összeadjuk a ciklomatikus komplexitásukat, akkor az meg fog egyezni minimum a proceduráliséval, amit növel a pluszban bevezetett absztrakciók komplexitása.

A masszív előnyt pedig az absztrakciók jelentik, hiszen nem kell átlátnod az egész programot implementációs szinten, pusztán azt az absztrakciós réteget, amivel éppen foglalkoznod kell.
Feljebb pedig ezt írtad:
Viszont egy fejlesztő ismeri ezeket a mintákat, nem kell megértenie a működését, a viszonyát a többi komponenshez, tudja milyen problémát akartak vele megoldani.
Tehát ha elkezdesz az OOP-vel foglalkozni, az évek alatt megtanulod a mintákat, és eljutsz oda, hogy el tudod készíteni a megfelelő absztrakciókat. Ezeket az absztrakciókat azok fogják megérteni, akik legalább annyi tudással rendelkeznek, mint te, "egy laikus számára tényleg bonyolultabbnak tűnhet a program" (idézet tőled).

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.
107

Igen, de ha összeadjuk a

MadBence · 2015. Júl. 22. (Sze), 15.35
Igen, de ha összeadjuk a ciklomatikus komplexitásukat, akkor az meg fog egyezni minimum a proceduráliséval, amit növel a pluszban bevezetett absztrakciók komplexitása.
Nyilván, komplexitást nem lehet eltüntetni, azt maga a program specifikációja adja :). Viszont ha meg szeretnéd érteni egy adott metódus működését, akkor sokkal könnyebb dolgod van, ha csak néhány lehetséges lefutást kell végig gondolnod.
Tehát ha elkezdesz az OOP-vel foglalkozni, az évek alatt megtanulod a mintákat, és eljutsz oda, hogy el tudod készíteni a megfelelő absztrakciókat. Ezeket az absztrakciókat azok fogják megérteni, akik legalább annyi tudással rendelkeznek, mint te, "egy laikus számára tényleg bonyolultabbnak tűnhet a program" (idézet tőled).

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 szeretnék olyan fejlesztővel dolgozni, aki ezekkel a legalapvetőbb mintákkal sincs tisztában. Egyébként ha procedurálisan fejlesztesz, ugyanezeket a mintákat fogod implementálni, maximum nem fogod nevén nevezni a gyereket (maguk a programtervezési minták is így alakultak ki).

Ezek után döntse el mindenki maga, melyiket választja.
+1, én nem akarok senkire ráerőltetni semmit
108

Én (mint mérnök) nem

Hidvégi Gábor · 2015. Júl. 22. (Sze), 15.53
Én (mint mérnök) nem szeretnék olyan fejlesztővel dolgozni, aki ezekkel a legalapvetőbb mintákkal sincs tisztában.
Mi számít alapvetőnek? A fenti procedurális kódban például nincs szükség sem gyárra, sem pedig kompozitra.

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.
109

Az oo kódban sincs

inf3rno · 2015. Júl. 22. (Sze), 16.20
Az oo kódban sincs kifejezetten szükség sem gyárra, sem kompozitra, megoldás függő a dolog. Akár teljesen ugyanúgy meg lehet csinálni if-else ágakkal, mint a struktúráltat, ha ahhoz van kedved.

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).
110

Csak hogy a te példáddal

inf3rno · 2015. Júl. 22. (Sze), 16.47
Csak hogy a te példáddal cáfoljam meg, amit állítasz:

function Konpozit() {
    this.objektumok = [];
}
Konpozit.prototype.betesz = function (objektum) {
    this.objektumok.push(objektum);
};
Konpozit.prototype.tedd = function () {
    for (var j = 0; j < this.objektumok.length(); j++) {
        this.objektumok[j].tedd();
    }
};

function Objektomgyar() {
}
Objektomgyar.prototype.gyart = function (feltetel, eredmeny) {
    var termek = new ObjektomgyarV1().gyart(feltetel, eredmeny);
    if (feltetel)
        return termek;
    var konpozit = new Konpozit();
    konpozit.betesz(termek);
    konpozit.betesz(new Amazt_tevo_osztaly);
    return konpozit;
};

function ObjektomgyarV1() {
}
ObjektomgyarV1.prototype.gyart = function (feltetel, eredmeny) {
    if (feltetel)
        return eredmeny ? new Ezt_tevo_osztaly : new Amazt_tevo_osztaly;
    else
        return new Emezt_tevo_osztaly;
};

//fő program  
var gyar = new Objektomgyar;
var objektum = gyar.gyart(feltetel, vizsgalat_eredmenye());
objektum.tedd(); 
ui: egyébként koMpozit, de lényegtelen...

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.
112

Nem cáfoltál meg sokat.

Hidvégi Gábor · 2015. Júl. 22. (Sze), 17.26
Nem cáfoltál meg sokat. Lehet, hogy a főprogram maradt, mint a második esetben, de ettől függetlenül egy új réteget csak be kellett hozni.
114

Sure, te viszont újraírásról

inf3rno · 2015. Júl. 22. (Sze), 22.51
Sure, te viszont újraírásról beszéltél, amiről szó sincs, csak kibővítettem a kódot, ennyi.
111

Én (mint mérnök) nem

inf3rno · 2015. Júl. 22. (Sze), 17.00
Én (mint mérnök) nem szeretnék olyan fejlesztővel dolgozni, aki ezekkel a legalapvetőbb mintákkal sincs tisztában. Egyébként ha procedurálisan fejlesztesz, ugyanezeket a mintákat fogod implementálni, maximum nem fogod nevén nevezni a gyereket (maguk a programtervezési minták is így alakultak ki).


+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.
105

var tovabbi_feltetel =

inf3rno · 2015. Júl. 22. (Sze), 14.17

	var tovabbi_feltetel = feltetel && vizsgalat_eredmenye();  
	var	futtato = new TevoFuttato();
	if (tovabbi_feltetel)
		futtato.push(new EztIgyTevo());
	if (!feltetel)
		futtato.push(new EmeztTevo());
	if (!feltetel || !tovabbi_feltetel)
		futtato.push(new AmaztTevo());
	futtato.futtat();
persze lehet valami modulválasztót is csinálni, hogy a feltételeket elrejtsük:

	var futtato = new TevoFuttato();
	var valaszto = new FeltetelAlapjanValaszto();
	futtato.hozzaad(valaszto.valaszt(feltetel));
	futtato.futtat();
további szép napot
113

Hogy írnád le az absztrakció

BlaZe · 2015. Júl. 22. (Sze), 22.34
Hogy írnád le az absztrakció fogalmát? Illetve a procedurális paradigma szerinted használ absztrakciókat? És ha igen, akkor mi a különbség az OOP és a procedurális absztrakció között? Valamint te magad szoktál-e absztrahálni?

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.

A fő program valóban jóval egyszerűbb és olvashatóbb, mint procedurálisan, hisz nem kell azzal foglalkoznom, hogy milyen módon állítom elő az objektumot.
Pont ez a lényeg :) Ezt akarjuk mindig elérni.
115

Az absztrakció

Hidvégi Gábor · 2015. Júl. 23. (Cs), 07.52
Az absztrakció információelvonás, hogy csak a lényeggel foglalkozzunk. Az OOP-s példában ez azt jelenti, hogy a fő programban csak azzal foglalkozunk, milyen parancsok futnak le egymás után.

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.

Illetve a procedurális paradigma szerinted használ absztrakciókat? És ha igen, akkor mi a különbség az OOP és a procedurális absztrakció között? Valamint te magad szoktál-e absztrahálni?
Idő hiányában: használ, nincs és igen. Más kérdés, hogy nem szeretem, és nagyon ritkán élek vele, mert információ tűnik el miatta.

Pont ez a lényeg :) Ezt akarjuk mindig elérni.
De milyen áron?
116

Más kérdés, hogy nem

BlaZe · 2015. Júl. 23. (Cs), 22.51
Más kérdés, hogy nem szeretem, és nagyon ritkán élek vele, mert információ tűnik el miatta.
Ez azt jelenti, hogy nem használsz függvényeket? Mit jelent az, hogy eltűnik információ? Az, hogy más absztrakciós szintre kerül valamilyen logika, nem jelenti azt, hogy elveszik. Egy hello world alatt is olyan mennyiségű absztrakció dolgozik, hogy nagyon komplex programot kell írj, ami azzal vetekszik. Akkor miről beszélünk?

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.
Szerintem az az ember még nem született meg, aki jobban megért 50ezer sor spagetti kódot, mint egy megfelelő absztrakciókkal megtervezett programot. Meg az se, aki absztrakciók nélkül képes megírni egy triviális komplexitás feletti kódot. Ha mégis, valószínűleg életfogytig állása lesz :)
117

Érdekes, mivel én v2-nél azt

pp · 2015. Júl. 26. (V), 16.44
Érdekes, mivel én v2-nél azt mondtam volna, hogy akkor a tedd_ezt() változott meg, és ahogy Te is átszervezted a kódodat, hisz a tedd_ezt() függvényt tedd_ezt_igy()-re nevezted át - jelezve, hogy ez már nem ugyan az a függvény.

É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.

Az OOP egyik alapvető eleme az absztrakció

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 :)

Amint változnak a feltételek, a kódot drasztikus mértékben kell módosítani.


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.

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.


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.

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.


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.
118

Nincs ezzel baj

Gixx · 2015. Júl. 27. (H), 15.09
Olyan, mintha egy vegetáriánus próbálná rábeszélni magát arra, hogy a rántott hús már pedig egy jó dolog. Látja, hogy sokan eszik és boldogok vele. Kipróbálja, de nem megy, nem ízlik, mást várt tőle, túl sok benne a hús. Felállít tehát olyan kontraproduktív bizonyításokat, amik igazolják számára, hogy nincs értelme próbálkozni, mert fölösleges, hús nélkül neki teljesebb a világ.

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 :)
119

Rofle :D

inf3rno · 2015. Júl. 27. (H), 21.31
Rofle :D Hát ez megkoronázta a napot! :D
120

Én a tedd_ezt()-be nyúltam

Hidvégi Gábor · 2015. Aug. 4. (K), 07.14
É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.
Egy ilyen döntéshez kevés volt az információ, ráadásul már nem emlékszem, hogy miért neveztem át a tedd_ezt() függvényt tedd_ezt_igy()-re, de valószínű, hogy elgépelés.

Tehát a V3 kód helyesen:
//fő program
if (feltetel) {
  var tovabbi_feltetel = vizsgalat_eredmenye();
  if (tovabbi_feltetel) {
    tedd_ezt();
  }
  else {
    tedd_amazt();
  }
}
else {
  tedd_emezt();
  tedd_amazt();
}


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 procedurálisnak hívott kódot csak kiegészíteni kellett az újabb feltételekkel és az új függvényhívásokkal.

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.
121

bind

zzrek · 2015. Aug. 7. (P), 10.29
Azon gondolkoztam, hogy az Object.Create-tel létrehozott objektumok metódusai miért nincsenek eleve magára az objektumra kapcsolva. Nem lenne hasznos egy ilyet használni helyette?

var objectcreator=function(c)
{
	var i,o=Object.create(c),m=Object.getOwnPropertyNames(c);
	for (i in m)
	{
	 if (typeof c[m[i]] == "function")
	 {
	  o[m[i]]=o[m[i]].bind(o);
	 }
	}
	return o;
}
Van ennek valami hátránya, ha így csinálom?
122

Hát pl ha prototípusos

inf3rno · 2015. Aug. 7. (P), 19.02
Hát pl ha prototípusos öröklődést használsz, akkor minden metódus a prototype-ra lesz bind-olva, ami nem túl nyerő, ha a példányokat akarnád használni...
123

Kétségtelen,

zzrek · 2015. Aug. 8. (Szo), 16.09
Kétségtelen, de egyébként sincs értelmes célpontra bind-olva.
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.