ugrás a tartalomhoz

PHP osztály - OOP v. csak annak látszik?

Kérésre törölve 11. · 2011. Május. 20. (P), 09.31
Abban szeretnék segítséget kérni, hogy a mellékelt osztály megfelel-e az objektum orientált programozás elveinek, vagy csak annyit csináltam, hogy egy függvény gyűjteményt beraktam egy class kulcsszóval kezdődő blokkba?
Bónusz kérdés: elkövettem-e benne valami komolyabb baklövést?
A User osztály egyébként csak arra szolgálna, hogy a kiválasztott azonosítóhoz tartozó adatokat betöltse + egy metódus segítségével tudja ellenőrizni a user jelszavát.
(későbbiekben ezen keresztül intézném az authentikációt, esetleg az authorizációt is)

ui: egyáltalán szabad itt ilyet kérdezni vagy e célra keressek más fórumot?

<?php
class User {

    const ID = 'id';
    const NAME = 'name';
    const PASSW = 'passw';
    const NICK = 'nick';

    protected static $db;        // Jó az nekem, hogy ez az adatbázis objektum statikus? (egyelőre úgy tűnik, igen)
    protected $prepSQL;

    protected $userdata=array();    // A user adatait tartalmazó tömb. Indexelés lehetőleg a fenti osztály konstansokkal!

    function show($s){ echo "<p>".$s."</p>"; }    // Debug célra, törölhető

    // A user adatok lekéréséhez használt SELECT összeállítása.
    // Paraméterbe a feltételt kell írni. A visszaadott objektum felhasználása(execute) előtt
    // szükséges egy bindValue vagy bindParam!
    // a bindColumn hívások a userdata tömbbe irányítják a SELECT kimenetének oszlopait.
    private function setupUsertabSQL($cond){
        $sql=self::$db->prepare("SELECT uid, mailaddr, nick, pwdhash FROM usertab WHERE ".$cond);
        $sql->bindColumn('uid',$this->userdata[self::ID],PDO::PARAM_INT);
        $sql->bindColumn('mailaddr',$this->userdata[self::NAME]);
        $sql->bindColumn('nick',$this->userdata[self::NICK]);
        $sql->bindColumn('pwdhash',$this->userdata[self::PASSW]);
        return $sql;
    }


    function __construct($p){    // Konstruktor. A paramétere lehet string, ekkor usernév/e-mail alapján szedi elő a usert az adatbázisból,
                                // vagy lehet integer, ekkor az UID alapján keres.
        try {
            if(!isset(self::$db)){
                try {
                    self::$db=new Database();    // Új adatbázis kapcsolat, ha még nincs

                // Ha valami gáz lenne...
                } catch(PDOException $e) {
                    echo "<p><b>Adatbázis hiba a kapcsolódáskor!</b></p>";
                    echo $e;
                    throw $e;
                }
            }
            // Mivel a metódus overload nem működik, kénytelen voltam így szétválasztani a két esetet (string v. int a paraméter)
            // Ezt is egyszerűsíteni kellene, mert ez a majdnem teljes copy/paste kicsit gusztustalan.
            if(is_string($p)){
                $this->show("User by name");
                $this->userdata[self::NAME]=$p;
                $this->prepSQL=$this->setupUsertabSQL("mailaddr=:mailaddr");    // A preparált SELECT-hez hozzárendelem a megfelelő változót,
                                                                                // paraméterként és végrehajtom
                $this->prepSQL->bindParam(':mailaddr',$this->userdata[self::NAME]);
            // Ha a paraméter integer, az eljárás nagyjából mint fent, csak usernév helyett UID-re keresek.
            } elseif(is_integer($p)) {
                $this->show("User by id");
                $this->userdata[self::ID]=$p;
                $this->prepSQL=$this->setupUsertabSQL("uid=:uid");
                $this->prepSQL->bindParam(':uid',$this->userdata[self::ID]);
            // Ha az osztály példányosításakor hibás paramétert kapott a konstruktor, akkor gáz van...
            } else {
                $this->userdata=array();
                $this->show("Hiba történt!");
                throw new Exception('Hibás paraméterezés');
            }
            $res=$this->prepSQL->execute();
            $res=$this->prepSQL->fetch(PDO::FETCH_BOUND);    // Elméletileg egyetlen sort adhat vissza a lekérdezés, talán nem ártana egy
                                                            // ellenőrzés és hibajelzés, ha többet. (a rowCount működése a PHP doksi szerint
                                                            // bizonytalan selectek esetében - viszont az adatbázisban egyedi kulcsként szerepelnek
                                                            // a lekérdezhető oszlopok, így lehet, hogy az ellenőrzés már túlzott paranoia)
                                                            // A fetch-nek átadott PDO::FETCH_BOUND jelzi, hogy a bindColumn segítségével
                                                            // párosított változókba kell tenni az eredményt.
            if(!$res){
                $this->show("sikertelen");
                $this->userdata=array();                // Ha sikertelen, akkor biztos-ami-biztos törlöm a userdata tartalmát, bár nem biztos,
                                                        // hogy van jelentősége - azt nem tudom, hogy egy Exception kiváltásán kívül hogyan
                                                        // tudnám megakadályozni az objektum létrehozását a konstruktorból.
            } else {
                $this->show("sikeres");                    // Ha sikeres a lekérdezés, akkor a userdata tömb már ki van töltve, nincs teendő
            }
        } catch(PDOException $e){
            echo "<br>adatbázis hiba<br>";
            throw $e;
        }
    }

    function getName(){ return $this->userdata[self::NAME]; }
    function getId(){ return $this->userdata[self::ID]; }
    function getData(){ return $this->userdata; }    // A user valamennyi adatának visszaadása, egyetlen tömbként (referenciák még mindig!!!)

    // Jelszó ellenőrzése. Mivel a "salt" értéke is benne van a hash-ben, egyszerű az ellenőrzés
    function checkPass($pw){
        return crypt($pw,$this->userdata[self::PASSW])==$this->userdata[self::PASSW];
    }
}

// Ideiglenesen itt van az adatbázist leíró osztály, ezt majd ki kell emelni, mert az adatbázis nem csak a userek adatait tartalmazhatja!
class Database extends PDO {
    function __construct(){
        return parent::__construct('mysql:host=localhost;dbname=adatbazis','minimaljoguuser','egyjelszo'/* , array( PDO::ATTR_PERSISTENT => true )*/ );
    }
}
?>

 
1

Az adatbázis kapcsolatnak nem

inf · 2011. Május. 20. (P), 10.18
Az adatbázis kapcsolatnak nem kell statikusnak lennie, inkább csinálj egy factory-t, amit dependency injection-nel beadsz minden osztálynak, és tőle kérj adatbázis kapcsolatot. Szóval az egész kapcsolódásos részt ki kell emelni az osztályból. Az User helyet meg UserDAO vagy ilyesmi lehetne az osztály neve, mert csak adatbázisos dolgok vannak benne.

A konstruktorba semmiképp nem jó, ha ilyesmiket teszel, ott néhány metódus meghívása kellene, hogy legyen, nem pedig normál kód. Ha van egy if-elseif-...-else szerkezeted, akkor minden benne lévő blokkot kiemelhetsz külön metódusba. Ha valami általános dologról van szó, akkor az összes ilyen metódus kimehet egy külön osztályba, vagy egy abstract class-be, amiből örököltetsz.

A show helyett unit testeket kéne írnod az osztályhoz.

A private helyett én protectedet használnék mindenhol a helyedben, ha meg nem akarsz továbbörökíteni egy osztályt, akkor meg tedd elé a final kulcsszót.
3

Köszi

Kérésre törölve 11. · 2011. Május. 20. (P), 10.50
Akkor ennek nekifutok még1x.
Egy dologgal vitatkoznék: a final protected látható a leszármazottak felől, ezt nem biztos, hogy akarom (pontosabban: egyáltalán nem szeretném, ez annyira "intim" rész... akar lenni... :) )
Maga az osztály egyelőre csak kezdemény (mást ne mondjak, normál körülmények közt ennél több paramétere van egy usernek), azért dobtam fel a kérdést, mert ha eleve rossz úton járok, akkor nem szeretnék ennél több munkát belefektetni.
Részemről folyt.köv. este, addig még rágódok egy sort azon amit írtál.
6

Final osztálynak nincsen

inf · 2011. Május. 20. (P), 13.01
Final osztálynak nincsen leszármazottja...
8

Nem leszármazottról beszéltem

Kérésre törölve 11. · 2011. Május. 20. (P), 15.18
A final csak a felüldefiniálást akadályozza meg (vagy még azt sem mindig? Kicsit kezd összefolyni a Java, a PHP, a Python, meg a Ruby :) )

<?php

class Teszt {
     final protected function Hello(){
         echo "<h1>HELLO!</h1>";
     }
 }

 class TesztGyerek extends Teszt {
     function metodus(){
         parent::Hello();
         echo "<h2>Gyerek</h2>";
     }
}

$t=new TesztGyerek();
$t->metodus();

A TesztGyerek hozzáfér a Teszt::Hello() metódushoz. Az eredeti kódban (számomra) épp az lenne a lényeg, hogy az utód azt a nagyon belső metódust ne tudja elérni.
(bocs, kicsit hosszú lett)
10

final class Test{} class

inf · 2011. Május. 20. (P), 15.35

final class Test{}
class OopsError extends Test {}
Nem tudom amúgy, hogy php-ban van e ilyen, nem követem a nyelv "fejlődését".
13

Ezzel viszont letiltom a Test

Kérésre törölve 11. · 2011. Május. 20. (P), 15.51
Ezzel viszont letiltom a Test osztály továbbörökítését, ami megintcsak nem célom.
14

Ez attól is függ, hogy mit

inf · 2011. Május. 20. (P), 16.03
Ez attól is függ, hogy mit akarsz private-be tenni. Nekem nem nagyon szokott ilyesmi lenni.
9

Egy apróságon elakadtam:

Kérésre törölve 11. · 2011. Május. 20. (P), 15.24
Egy apróságon elakadtam: miért ne legyen a konstruktorban hosszabb kód? Olvashatóság miatt? Egyéb szempontból? Anno úgy tanultam, hogy csak olyasmit érdemes külön függvénybe/eljárásba (alias "szubrutin") pakolni, ha több helyről akarom használni. Ennek egyik oka volt, hogy drága játék egy-egy függvény hívása (igaz, jelen esetben nem érdemes a performanciára gyakorolt negatív hatásokkal foglalkozni :) )
11

Egy-egy függvény hívása

inf · 2011. Május. 20. (P), 15.43
Egy-egy függvény hívása egyáltalán nem drága. Legalábbis ezt előre nem lehet tudni, hogy mennyire drága lesz. Ha elolvasol bármilyen komolyabb könyvet a témában, akkor mindenhol kihangsúlyozzák, hogy előbb kell fejleszteni (minél átláthatóbb kódot), utána pedig optimalizálni. Az optimalizálásnál ráküldesz egy profilert a kódra, ami megmutatja, hogy mi az, ami nagyon lassít, vagy hol a szűk keresztmetszet, és csak ott nyúlsz bele a kódba. Elvileg van olyan, hogy optimization patterns, de még nem volt időm belenézni ezekbe a mintákba, szóval egyelőre ehhez a témakörhöz annyira nem értek.

Egyébként igen, az olvashatóság miatt kell külön tenni. Van egy optimális függvényhossz, amit azért érdemes tartani. Annál a legolvashatóbb a kódod. Ha annál hosszabb függvények vannak, akkor nem látod ránézésre, hogy mit csinálnak, ha annál rövidebbek, akkor meg képtelen vagy normális nevet adni nekik.
A másik ami miatt én külön szoktam tenni, hogyha esetleg úgy döntök, hogy mégsem konstruktorból hívom meg az adott dolgot, akkor már ne kelljen átírni. A private helyett is azért szoktam protectedet használni, hogyha mégis örököltetni akarok, akkor ne kelljen mindenhol átírnom...

Osztályoknál egyébként metódusok vannak és nem függvények/szubrutinok... :-)
12

Osztályoknál egyébként

Kérésre törölve 11. · 2011. Május. 20. (P), 15.50
Osztályoknál egyébként metódusok vannak és nem függvények/szubrutinok... :-)


Tudom, de amikor én programozni tanultam, akkor volt COBOL,PL/I, FORTRAN, meg Assembly. (meg néhány, ezekhez hasonló nyelv, amikkel nem foglalkoztam)
Mivel az akkor tanultakra céloztam, nem emlegethettem metódust.
És általában azért "drága" (legalábbis az akkori programnyelvek esetében), mert a függvényhívás kapásból azzal indít, hogy egy ugrás az adott kódrészletre, a stack macerálása (visszatérési cím, dinamikus változóknak tárhely foglalás, ilyesmi...) plusz a függvény/szubrutin végén, visszatérés előtt némi nagytakarítás. Ez mind megspórolható volt, ha a kódot közvetlenül oda írom, ahol használom.
15

Én ezt elhiszem, de azóta

inf · 2011. Május. 20. (P), 16.04
Én ezt elhiszem, de azóta fejlődtek annyit a processzorok, hogy kiröhögnek, ha te odateszel egy függvényt... :-)
Persze java esetében igazából mindegy, mert ott úgyis befordítja a kódot a java engine nyelvére. A php esetében nyilván valamennyit lassítani fog, de mondjuk egy adatbázis kérés több időt fog elvenni, mint a legtöbb függvényed együtt...
2

Szerintem ez megfelel az OOP

bb0072 · 2011. Május. 20. (P), 10.46
Szerintem ez megfelel az OOP elveinek az alábbi megjegyzéssel: az egyik alapelv a változók és a függvények elérhetőségének lehető legszűkebbre vétele. Tehát, ha nincsenek a User-ből leszármazott osztályaid, akkor protected helyett használj mindenhol private-t.
4

Elképzeléseim szerint lesz

Kérésre törölve 11. · 2011. Május. 20. (P), 10.53
Elképzeléseim szerint lesz néhány leszármazott - a végső User valószínűleg abstract lesz, de... mit mondjak, azt képzeltem, egyszerűbb lesz. Túl sok időt hagytam ki az utolsó programozói munkám óta. :(
Legnagyobb bajom ezzel az egész OOP-vel, hogy fogalmam sincs, mikor, mit érdemes önálló osztályba tenni.

Tankjú!
5

főnevek

razielanarki · 2011. Május. 20. (P), 12.11
Van egy ilyen tervezési hozzáállás, hogy ha megvan a specifikáció, akkor az abban szereplő főnevek nagy valószínűséggel osztályok lesznek (pl, User, Kosár, Rendelés, Értékelés, Hozzászólás stb).

persze ezt szó szerint venni botorság lenne, de el lehet indulni rajta.
7

Ha úgy gondolod, hogy egy

inf · 2011. Május. 20. (P), 13.06
Ha úgy gondolod, hogy egy osztályban bizonyos metódusok egy fogalom köré csoportosulnak, akkor abból már lehet új osztályt csinálni. Ha 10-nél több metódusod van egyetlen osztályban, akkor sok esetben el kell gondolkodni, hogy ki lehet e emelni új osztályt. Ha 50 sornál hosszabb metódusod van, akkor meg azon kell elgondolkodni, hogy ki lehet e emelni belőle új metódusokat. Az, hogy lehet e, vagy sem azon múlik, hogy mennyire logikus. Ha nem tudsz találó nevet adni egy osztálynak, vagy metódusnak, akkor valószínűleg nem érdemes létrehozni azt. (Persze ez az egész rutin kérdése is. Érdemes lenne elolvasnod néhány refaktorálással foglalkozó könyvet, azok sokat segítenének ebben.)
16

Zavart érzek az erőben :-)

Kérésre törölve 11. · 2011. Május. 21. (Szo), 07.34
Átnézve a fenti osztály definíciót, rájöttem, hogy vannak benne kisebb-nagyobb hiányosságok. Például ez:
OO környezetben mi a szokásos eljárás arra az esetre, ha pl. egy usert objektumként kezelek, ami kizárólag a már létező usereket hivatott modellezni(*), de a példányosításkor kiderül, hogy nem szerepel az adatbázisban?

- létrehozom az objektumot üres adatlappal?
- mint fent, de dobok egy Exceptiont?
- a new-val történő példányosítás helyett... nem jut eszembe a pontos kifejezés, valami "factory" rémlik: statikus metódusból gyártom le a példányt és NULL-t adok vissza objektum helyett, ha nem tudom létrehozni?
- esetleg van lehetőség rá, hogy a new adjon vissza NULL-t? Ezt hogy lehet kivitelezni? Gúgli nem segített (nem is tudom igazán, hogy mit kellett volna keresnem)

Ha jól sejtem, mindegyik variációra van példa (kivéve tán az utolsót, mert az mintha nem létezne PHP-ben) és inkább a szándékaim alapján kellene kiválasztani a megfelelő módszert, nem lehet egyik v. másik módra ráfogni, hogy "ez az üdvözítő megoldás".


* - abban sem vagyok biztos, hogy egy ilyen objektum, mint pl. egy felhasználó esetében érdemes foglalkozni a létrehozáskor azzal, hogy szerepel-e az adatbázisban v. sem. Utóbbi esetben két utolsó variáció eleve kiesik.
17

- mint fent, de dobok egy

inf · 2011. Május. 21. (Szo), 08.11
- mint fent, de dobok egy Exceptiont?

Én erre szavazok, vagy példányosítás előtt ellenőrzöd az adatbázist...
18

Ha szigorúan veszem azt, amit

Kérésre törölve 11. · 2011. Május. 21. (Szo), 08.27
Ha szigorúan veszem azt, amit az OO programok, osztályok zártságáról eddig tanulgattam, akkor az adatbázist nem ellenőrizhetem példányosítás előtt... hacsak... hacsak nem gyártok egy statikus metódust a User osztályba, aki ezt megteszi. Hm. Kösz a tippet! :)
Végülis az Exception is szimpátikus. :)
19

Factory

Poetro · 2011. Május. 21. (Szo), 12.09
A factory simán ellenőrízhet példányosítás előtt. És ha nem létezik az adatbázisban, akkor például nem hoz létre példányt, vagy másfajta objektumot hoz létre.
20

Igen, ez tiszta, de fentebb,

Kérésre törölve 11. · 2011. Május. 21. (Szo), 12.31
Igen, ez tiszta, de fentebb, inf3rno ezt a lehetőséget gyakorlatilag elvetette (én még nem teljesen, miután kissé utánaolvastam), ezért kezdtem gondolkodni más megoldáson.
21

vagy példányosítás előtt

inf · 2011. Május. 21. (Szo), 12.42
vagy példányosítás előtt ellenőrzöd az adatbázist


Nem tűnik úgy, mintha elvetettem volna... :-)
Mindkettő jó megoldás, ízlés kérdése szerintem...

Mondjuk ha csinálsz egy User példányt, aminél beállítod az email címet meg a jelszót, aztán azzal próbálsz beloginolni, akkor az sem fog feltétlen létezni az adatbázisban, a regisztrálásnál szintén ez a helyzet....
22

Példányosítás előtt - ebből

Kérésre törölve 11. · 2011. Május. 21. (Szo), 13.05
Példányosítás előtt - ebből nekem az jött le, hogy objektumtól függetlenül ellenőrizzem. :)
---------
De még1x: ez a fenti csak egy tesztelésre szánt kezdemény, inkább csak próbálgatom, mit sikerült megtanulni az OOP dolgaiból, felhasználni közvetlenül nem akarom.

Regisztráció eleve elég zűrös ügylet, ha nem akarok spam botokat regisztrálni, ha valamelyest biztonságos oldalt akarok, akkor a login is elég macerás... szóval ez a class még elég kevés.
23

Majd írok cikket a

inf · 2011. Május. 21. (Szo), 19.38
Majd írok cikket a refaktorálásról amint lediplomáztam, és lesz valamennyi szabadidőm. Abból szerintem több ki fog derülni számodra.
24

Átfutva a kommentek nagy részét

prom3theus · 2011. Május. 23. (H), 03.34
Átfutva a kommentek nagy részét, szeretnék rávilágítani pár dologra.

A fenti osztályodról nem mondható el, hogy megfelel-e az OO elveknek, mert az OO elvekben az objektumok közötti sokféle reláción van a legnagyobb hangsúly. Azaz, hogy egy osztály leszármazottja-e egy másiknak vagy sem, egy objektum hogyan kapcsolódik egy másikhoz, egy objektumot létrehoz-e egy másik objektum, stb. és ezek miértjei. Főként a miértjei. Az objektum orientáltság egy fejlesztési szemléletmód, amiben a hansúly az elvonatkoztatáson van:
- Objektumként értelmezek mindent olyan elemet (a specifikációban, vagy a létező világban) ami feladatok jól meghatározott, logikailag összefüggő csoportját látja el és kezeli az ehhez szükséges adatokat. Példálul a felhasználó, "amiről" tudjuk, hogy látogatja az oldalt, kattintgat, űrlapokat tölt ki, stb. Vagy egy labdát, ami tud pattogni és gurulni.
- Megállapítom, hogy melyik objektum milyen tulajdonságokkal bír és milyen műveleteket végezhet. Tudom, hogy a felhasználó látogatja az oldalt és vannak (lehetnek) neki profiladatai. Kell tehát rögzíteni az ő profiladatait, az utolsó látogatása idejét (ha ez érdekes adat), tudom, hogy bejelentkezhet és kijelentkezhet (metódusok), hogy adatokat küldhet például űrlapokon keresztül, amelyeket fel kell dolgozni. A labdáról elmondható, hogy van felszíne, átmérője és tömege (tulajdonságok), el lehet rúgni vagy gurítani, pattanhat (metódusok).
- Megpróbálok elvonatkoztatni, prototipizálni. A felhasználóról elmondható, hogy vannak adatai, amelyek állíthatók, a felhasználó maga pedig törölhető is ezért (vagy nem, ez specifikáció függő persze) és elmondható, hogy bizonyos egyéb műveleteket is lehet vele végezni. Ez egy szülő osztály leírása már. A labdáról elmondható, hogy elhelyezkedik valahol a térben.
- Így kialakítok egy alapvető osztályt, amely kapcsolatban áll egy adattárolóval (jellemzően egy adatbázis kiszolgálóval) és pontosan egy rekordot tartalmaz adatként, mint tulajdonság, valamint néhány metódust, amivel az adatokon végezzük a műveleteket. A labdás példáról most leszállok.
- A szülőosztályban felmerül az adatbázis, mint jellemző. Ez felveti annak a szükségességét, hogy rendelkezzünk egy adatbázis osztállyal, amely kapcsolatot biztosít a rendszerünk és az adatbázis kiszolgáló között, lehetővé téve az adatok manipulálását és lekérését.
- Az egyszem adatunk egy rekord szerkezet. Ez felveti a szükségességét annak, hogy legyen egy osztályunk, ami ilyen adatszerkezeteket kezel.
- Máris kiderült, hogy a felhasználó OO szemléletű megvalósítása magával vonta +3 osztály szükségességét (a szülőosztályét, az adatbázis elérőét és a rekordot kezelőét).

Eztán azt mondhatjuk, hogy a felhasználó osztályunknak van egy elvont szülője, amely rendelkezik az alábbiakkal:
- Egy tulajdonság, amely az adatbázis kiszolgáló objektumra mutat
- Egy tulajdonság, amely tárolja és kezeli az adatbázisból kivett pontosan egy darab rekordot
- Egy metódus, amellyel beemeljük a megfelelő adatsort a rekord tulajdonságunkba
- Egy konstruktor, amelynek meghatározható, hogy melyik rekordot kell majd beemelni

Ekkor világossá válik, hogy rekord kezelő osztály (amelyet a rekord kezelésére kell majd példányosítanunk) az alábbiakkal kell hogy rendelkezzen:
- Egy asszociatív tömb (gyűjtemény, hash, stb.), amelyben a beemelt adatokat tároljuk.
- Egy jelző tulajdonság, ami "igaz", ha az adatok módosultak.
- Egy tulajdonság, amely az adatbázis kiszolgáló objektumra mutat.
- Egy metódus, amellyel az adatbeemelés megtörténik a megfelelő azonosító alapján.
- Egy metódus, amellyel az asszociatív tömb (vagy annak konkrét eleme - megoldás függő) lekérdezhető.
- Egy metódus, amellyel a tömb elemei módosíthatóak. Be kell állítja a jelző tulajdonságot.
- Egy metódus, amellyel a rekord törölhető az adatbázisból.
- Egy metódus, amellyel a rekord mentésre kerül az adatbázisban, ha a jelző tulajdonság szerint az adatok módosultak.
- Egy konstruktor, amely a megkapja és tárolja az adatbázis elérő objektum referenciáját, ill. egy kapott azonosítóval meghívja az adatbetöltő metódust.
- Egy destruktor, amely az objektum megszűntetése előtt a biztonság kedvéért meghívja a rekord tároló eljárást. Itt még szorgalmi feladat megoldani, hogy hogyan kezelje az objektum azt, ha a rekordot törölték (gyaníthatóan ehhez is jelző kell, amit az adatmentő ellenőrizhet, kerülendő azt, hogy nem létező rekordot akarjunk menteni).

Az adatbázis elérő osztályra és objektumra nem térek ki, PHP-nál maradva legyen ez a PDO osztály (lásd php.net/pdo).

Ez után visszatérünk a felhasználóhoz és megállapítjuk a többlet képességeit a szülőosztályhoz képest:
- Adott egy jellemzője, amely az utolsó bejelentkezése idejét tárolja
- Képes bejelentkezni az azonosítója és jelszava segítségével
- Képes kijelentkezni
- Megállapítható róla, hogy be van-e jelentkezve vagy sem (elérhető-e hozzá betöltött rekord, vagy sem)
- Képes megállapítani és betölteni az utolsó bejelentkezése időpontját
- Képes egyéb adatokat küldeni (amelyeket szűrni kell bizonyos szempontok szerint - tegyük hozzá, a legtöbb keretrendszer ezt a felhasználói ténykedést nem a felhasználó osztályába sorolja, hanem külön segédosztályokat hoz létre a feladatra)
- Kell majd egy konstruktor, amely elvégzi a szükséges inicializáló műveleteket (ellenőrzi a bejelentkezettséget pl a munkafolyamati változóban tárolt érték alapján és betölti az utolsó bejelentkezés idejét a megfelelő tulajdonságba, majd meghívja a szülőosztály konstruktorát [amely betölti a felhasználó rekordját] - sarkítva).

Eztán elmondhatjuk, hogy a felhasználót helyesen elvonatkoztatva és a problémát alaposan körüljárva kaptunk 3 újrahasznosítható osztályt. És ezen a ponton révbe értünk, ez az OO szemlélet egyik legfontosabb hasznossága: kiemelhetünk osztályokat és újrahasznosíthatjuk anélkül, hogy az adott osztályoknak tudniuk kellene azt, hogy ki/mi használja őket. A rekord szempontjából lényegtelen, hogy felhasználó rekordjáról beszélünk, vagy egy esemény (pl naptári bejegyzés) rekordjáról. Az adatbázis elérő szempontjából lényegtelen, hogy rekord kezelésére vagy csak egy egyszerű időbélyeg kiolvasására használjuk. Stb.

Ha eszerint nézem a fenti kódot, akkor valóban nem felel meg az OO szemléletnek (bár az adatbázis leválasztása egy jópont!).

Még valami: szóba került jóhamar a Factory minta. Szerintem mielőtt beleveszne az ember ezekbe (az egyébként igen hasznos) módszertanokba, érdemes megpróbálni ezek ismerete nélkül megoldani feladatokat. Mert az OO tervezési minták nagyon elvontak, és aki érteni is akarja őket, nem csak megpróbálni alkalmazni ("hátha ez lesz a jó" alapon), az először sajátítsa el helyesen az OO szemléletű elvonatkoztatás módszereit.

Feszültség oldásnak: a világon valójában minden leírható OO szemlélettel - kivéve a Nőt ;-)
(inkább úgy mondanám, hogy eddig egyik állításomat sem cáfolta a gyakorlat :D, jellemzően determinisztikus dolgok írhatóak le OO megközelítésből, a Nő archetipikusan nem mindig ilyen :-) - bocs lányok, nem cinkelek, sőt, ez olyan dolog, amiért nagyon tudlak titeket tisztelni :-))
25

Most azon gondolkodom, hogy

Kérésre törölve 11. · 2011. Május. 23. (H), 08.01
Most azon gondolkodom, hogy merjek-e reagálni azokra a dolgokra, amikkel nem értek teljesen egyet. Rossz tapasztalat, hogy ha valamit másképp látok, általában flame-be fordul a társalgás, mert elbeszélünk egymás mellett. :)


----
Egyelőre csak annyit: nem szándékom "hátha ez is jó lesz" alapon dolgozni, de korábban is írtam, hogy még csak ismerkedem az egész témával.
Az OOP v. csak annak látszik kérdés meg mindössze onnan jött, hogy néhány fórumon találkoztam olyan reakcióval, hogy a kérdező nem objektum orientált programot ír, csak class-okba csomagolja a függvényeit. :)

Köszi szépen, hogy ennyi időt fordítottál rá, megpróbálom alaposabban feldolgozni amit írtál, aztán folyt.köv. (ha le tudom úgy írni, hogy ne tűnjön kötekedésnek :) )

----

Végeredményben ott tartok (tképp ezen "rugózok" közel fél éve), hogy nem igazán tudom az (egyébként sem túl kidolgozott) elképzeléseimet objektumokra/osztályokra lebontani. Többek közt azért sem, mert egyik végletből: minél több dolgot összezsúfolni egyetlen objektumban, a másikba esek: olyan szinten kezelni objektumként mindent, hogy pl. egy usernév is önálló objektum legyen. Persze ez csak egy kiragadott példa, rengeteg ilyen "apróság" van, amin nem tudok átlépni.
27

Köszönöm a válaszodat. Ha

prom3theus · 2011. Május. 24. (K), 10.51
Köszönöm a válaszodat. Ha találtál kritizálható pontot a hozzászólásomban, nyugodtan bonyolódj velem vitába, mert a jó pap is holtig tanul - tehát én is. Nagyjából mintegy 11-12 éve kezdtem el megismerkedni az OO szemlélettel, egy ideje "tűzzel-vassal" (inkább valójában foggal-körömmel :D) küzdök az ellen a jelenség ellen, amit te is írtál (bepakolni a kódot egy class blokkba) és ennek egyéb következményeivel szemben (pl.: még OO-bb az, ha minden PHP fájlunkat class-okba szervezzük).

Persze, veled nem is ez a helyzet, ez már a topik elnevezéséből világos volt, de szerettem volna átadni egy kevés tapasztalatot azért, hogy világosabb legyen, mitől lesz OO szemléletű egy kód (vagy bármi).

A mostanában divatos tervezési mintákkal való dobálózás ellen is ugyanilyen elánnal küzdök, és ennek az oka pont az, hogy tapasztalataim szerint 5 OO fejlesztőből csak 2-nek van fogalma arról, mi az OO valójában. A szemléletmód ismerete nélkül alkalmazott patternek pedig életveszélyessé válnak, mint a napos csibe kezében a gépfegyver. Általában az ilyen pattern-bozótharcosok munkája után következnek a végeláthatatlan refaktorálások, amik egyébként is gyorsan vezetnek egy egész rendszer újraprogramozásának igényéhez. Utóbbival nincs feltétlenül baj, de kerülni kell azt, hogy egy terméket a megrendelő (ügyfél vagy munkáltató) kétszer fizessen ki pusztán azért, mert valakik valamikor nem értettek meg egy fejlesztési szemléletmódot, amiből aztán olyan rendszert építettek, mint egyes angol házak, ahol a második szint folyosójáról nyíló ajtó a (földszinti) kertre nyílik, nem egy szobára.

Ezért igyekeztem úgy válaszolni, hogy neked is válaszoljak, és azoknak is, akik csak olvassák a fórumot és nem értik igazán, mitől OO valami.

Az egyébként egy elméleti félreértésből ered, ha valaki annyira OO akar lenni, hogy egy usernévre is osztályt akar írni. Az OO szemléletmód eleve abból indul ki, hogy minden dolog leírható objektumként, az objektum maga pedig egy osztályból (vagy prototípusból) kerül példányosításra - ergo minden leírható osztályként vagy prototípusként. Azokon a nyelveken is, amelyek nem támogatnak semmilyen OO szintaktikai elemet. Mert ez szemléletmód, nem pedig szintaktika. Szóval a fenti elvet sokan úgy fordítják le, hogy akkor mindennek, még a primitív típusoknak is osztályban a helyük. Ez pedig baromság. Az elv helyes értelmezése az, hogy a primitív típust primitív osztály valósítja meg, amely rendelkezik egy értéktároló tulajdonsággal és számos értékadó, értéklekérő metódussal, valamint általában egy típus lekérésre alkalmas metódussal. Ezeket a nyelv elfedi és a nyelv implementációjától függően vagy lehetőség van felülbírálni (operator overloading pl.) vagy nem. Egy primitív típus tehát felfogható osztálynak akkor is, ha egyébként nem ebben a szemléletben készült és valósult meg nyelvi szinten. Hacsak nem szükséges kiterjeszteni az adott primitív típust, akkor nincs tehát szükség osztály keretet írni rá. Persze, sok esetben hasznos lenne, de törekedni kell a nyelv szemléletmódjának koherens alkalmazására is, mivel a legtöbb esetben ez jobb futási teljesítményhez vezet, de legalábbis többnyire kevesebb hibát tesz lehetővé.
31

Kis türelmet kérek, még nem

Kérésre törölve 11. · 2011. Május. 24. (K), 16.14
Kis türelmet kérek, még nem volt lelkierőm, hogy kellő alapossággal elmélyedjek a témában... (ért egy kis kínos meglepetés tegnap, ami finoman szólva elvette a kedvem mindentől, de majd megpróbálom...)

Szerk.: usernév, mint objektum. Persze, ahogy én írtam, az hülyeség - csak próbáltam érzékeltetni, mennyire bele tudok kavarodni az objektum orientált programozásról olvasottakba. :)
Annyi realitása azért van neki, hogy pl. Java-ban, Ruby-ban, meg tán Python-ban is, a stringek eleve objektumként működnek.
Más kérdés, hogy ezt nem kell feltétlenül megírnom. Bár ha arra gondolok, hogy esetemben ez leginkább egy e-mail cím lenne, akkor máris nem tűnik akkora marhaságnak (legalábbis számomra) - lehetne neki pl. ellenőrző metódusa, ami akár szintaktikailag, akár fizikailag ellenőrzi, hogy valódi, működőképes cím-e... és több hirtelen nem jut eszembe. :)
26

Hátja, de ezt olyan hosszú

inf · 2011. Május. 23. (H), 11.13
Hátja, de ezt olyan hosszú leírni, hogy elkezdtem, aztán inkább elmentem sörözni a haverokkal helyette :D :D :D
28

Meg tudlak érteni, de hajnali

prom3theus · 2011. Május. 24. (K), 10.56
Meg tudlak érteni, de hajnali fél 4-kor nekem ilyen választási lehetőségem nem volt sajnos :)
29

Ne sajnáld, én speciel

Kérésre törölve 11. · 2011. Május. 24. (K), 11.07
Ne sajnáld, én speciel kifejezetten örülök, hogy kocsmázás helyett inkább erre fordítottál egy kis időt. ;-)
30

Részvét :D Majd legközelebb

inf · 2011. Május. 24. (K), 12.55
Részvét :D Majd legközelebb több szerencséd lesz :D
32

Eredetileg vitatkozni

Kérésre törölve 11. · 2011. Május. 24. (K), 18.28
Eredetileg vitatkozni akartam, mert azt hiszem, te is mást vársz egy web lap userét modellező objektumtól, meg én is mást. Kezdek rájönni, hogy ilyen részletekbe felesleges lenne belemászni, amikor pl. egy szimpla logolás, ami adatbázisba nyomja a logot, alapjaiban forgatja fel az eddigi elképzeléseimet mindenről. :)
Próbáltam az általad írtak alapján átgondolni azt, amit eddig elkövettem ("kissé" már eltér a topic fejlécében látható kódtól) és közben kipróbáltam, mi történik a destructorban, ha megkérem, hogy írjon magáról egy sort egy log táblába.
Most beleerőszakoltam egy metódusba, de egyértelmű, hogy pl. a log írása önálló osztályért, objektum(ok)ért ordít... Hajjjajjjjjj... és még bele se kezdtem igazán... Azt hiszem, ez picit nagyobb falat lesz, mint terveztem. De OOP lesz, ha beledöglök is! :)

(<panaszkodás on>látszik: amit anno műveltem, azt ma úgy hívják, hogy code monkey... magasabb szintű tervezéshez semmi közöm sem volt :) :( )
33

Ha sok objektumod van, akkor

inf · 2011. Május. 24. (K), 18.39
Ha sok objektumod van, akkor én első körben megnézném a létrehozó mintákat (creation patterns) és a dependency injectiont. Ha már ezeket érted, akkor lehet továbblépni a komplikáltabb dolgok felé...

Visszatérve a konstruktorod szétbontására:
Az is_string-es rész kapásból egy metódus, az is_integeres rész szintén egy metódus... Az if-es részt bármilyen jóérzésű ember feltenné egy olyan osztályba, ami az User-t hívja, szóval egy controllerbe.

A kapcsolat létrehozásáért egy factory kéne, hogy felelős legyen, nem pedig az User (főleg nem statikusan...), a factory-t pedig érdemes beinjektálni. Én ilyenkor sokszor csinálok egy olyan factory-t, ami factory-kat gyárt, és azt injektálom mindenhova.

Konstansok helyett én csinálnék egy fieldMap-et, ami alapján a bindelésnél kapásból meg lenne az adott mező típusa, és nem kéne kézzel beírni.

(off: Gyakorlatilag az egész rossz, amit írtál, de legalább ki lehet indulni belőle :D)