ugrás a tartalomhoz

Absztrakt

tóthika · 2015. Júl. 22. (Sze), 00.17
Sziasztok!

Egy projekt kapcsán merült fel bennem a kérdés, hogy hogyan lehetne a globális adatteret nem beszennyezni mindenféle változóval úgy, hogy a függvények ilyenfajta, globális adattérből történő változólekérdezése pontosan legyen definiálva. Ráakasztanék egy bemeneti, és esetleg egy kimeneti 'felületet'.
Első gondolatra az Objektumorientált programozás vetődött fel, akkor viszont át kellene rendezni az egészet.

Ti mit gondoltok erről?
 
1

Globális

Hidvégi Gábor · 2015. Júl. 22. (Sze), 07.53
Miért okoz számodra gondot a globális névtér "beszennyezése"? Ha jól nevezed el a változóidat, esetleg típus szerinti prefixeket használsz, pl. $db_kapcsolat, $oldal_tartalom, akkor nem lesznek ütközések.
2

Egyszer eljut az ember erre a

bamegakapa · 2015. Júl. 22. (Sze), 08.24
Egyszer eljut az ember erre a pontra, és ez remek. Itt az ideje, hogy elmerülj az OOP-ben, megvizsgáld, hogy milyen megoldásokat kínál az ilyen és más felmerülő problémákra. Ne engedj annak a felednek, ami az új szemléletek ellen tiltakozik, mert akkor nem leszel nyitott és megrekedsz.

Eltarthat egy ideig, mire ráállsz, kitartás. Ezen sokan átestünk.
3

Természetes, hogy át kell

inf · 2015. Júl. 22. (Sze), 16.16
Természetes, hogy át kell rendezni az egészet, mert az oo teljesen más szemléletet kíván, mint a struktúrált programozás. Az oo-nál általában laza csatolásra törekszünk és csak ritkán használunk szoros csatolást, a struktúráltnál viszont inkább szoros csatolásra törekszünk, és csak elvétve használunk laza csatolást. Ebből nyilvánvalóan adódik, hogy az oo kód rugalmasabb, mert a laza csatolás miatt könnyen cserélhetőek az elemei.


  • struktúrált, szorosan csatolt
    
    $a = csinaljAt(1, 2, 3);
    
    function csinaljAt($x, $y, $z){
    	$b = csinaljBt($y, $z);
    	return $x + $b;
    }
    
    function csinaljBt($y, $z){
    	return $y - $z;
    }
    
  • struktúrált, lazán csatolt (inversion of conrol)
    
    $a = csinaljAt(1, 2, 3, 'csinaljBtKivonassal');
    
    function csinaljAt($x, $y, $z, $csinaljBt){
    	$b = $csinaljBt($y, $z);
    	return $x + $b;
    }
    
    function csinaljBtKivonassal($y, $z){
    	return $y - $z;
    }
    
  • oo szorosan csatolt
    
    $atCsinalo = new AtCsinalo();
    $a = $atCsinalo->csinaljAt(1, 2, 3);
    
    class AtCsinalo {
    	public function csinaljAt($x, $y, $z){
    		$btCsinalo = new BtCsinalo();
    		$b = $btCsinalo->csinaljBt($y, $z);
    		return $x + $b;
    	}	
    }
    
    class BtCsinalo {
    	public function csinaljBt($y, $z){
    		return $y - $z;
    	}
    }
    
  • oo lazábban csatolt (inversion of control)
    
    $atCsinalo = new AtCsinalo();
    $a = $atCsinalo->csinaljAt(1, 2, 3, new BtCsinalo());
    
    class AtCsinalo {
    	public function csinaljAt($x, $y, $z, BtCsinalo $btCsinalo){
    		$b = $btCsinalo->csinaljBt($y, $z);
    		return $x + $b;
    	}	
    }
    
    class BtCsinalo {
    	public function csinaljBt($y, $z){
    		return $y - $z;
    	}
    }
    
  • oo lazán csatolt, mert itt már csak interface-nek való megfelelést és nem osztálynak való megfelelést szabunk meg, így az osztály szabadon cserélhető, amíg implementálja az interface-t (dependency inversion)
    
    
    
    $atCsinalo = new AtCsinalo();
    $a = $atCsinalo->csinaljAt(1, 2, 3, new BtKivonassalCsinalo());
    
    class AtCsinalo {
    	public function csinaljAt($x, $y, $z, BtCsinalo $btCsinalo){
    		$b = $btCsinalo->csinaljBt($y, $z);
    		return $x + $b;
    	}	
    }
    
    interface BtCsinalo {
    	public function csinaljBt($y, $z);
    }
    
    class BtKivonassalCsinalo implements BtCsinalo {
    	public function csinaljBt($y, $z){
    		return $y - $z;
    	}
    }
    
  • oo lazán csatolt, a függőségek megadása jobban nyomon követhető, ha az objektum felépítésénél történik, nem pedig hívásnál (dependency injection)
    
    $atCsinalo = new AtCsinalo();
    $atCsinalo->setBtCsinalo(new BtKivonassalCsinalo());
    $a = $atCsinalo->csinaljAt(1, 2, 3);
    
    class AtCsinalo {
    	protected $btCsinalo;
    	
    	public function setBtCsinalo(BtCsinalo $btCsinalo){
    		$this->btCsinalo = $btCsinalo;
    	}
    
    	public function csinaljAt($x, $y, $z){
    		$b = $this->btCsinalo->csinaljBt($y, $z);
    		return $x + $b;
    	}	
    }
    
    interface BtCsinalo {
    	public function csinaljBt($y, $z);
    }
    
    class BtKivonassalCsinalo implements BtCsinalo {
    	public function csinaljBt($y, $z){
    		return $y - $z;
    	}
    }
    
  • oo lazán csatolt, a példányosítást érdemes egy külön container osztályra bízni, ami csak azzal foglalkozik, hogy példányosít és a függőségeket beállítja, így ha többféle a és b csinálót akarunk összepárosítani, akkor adhatunk a container-nek is interface-t, és csinálhatunk többféle implementációt neki (dependency injection container vagy inversion of control container)
    
    $container = new ABContainer();
    $atCsinalo = $container->getAtCsinalo();
    $a = $atCsinalo->csinaljAt(1, 2, 3);
    
    class ABContainer {
    	public function getAtCsinalo(){
    		$atCsinalo = new AtCsinalo();
    		$atCsinalo->setBtCsinalo($this->getBtCsinalo());
    		return $atCsinalo;
    	}
    	
    	public function getBtCsinalo(){
    		return new BtKivonassalCsinalo();
    	}
    }
    
    class AtCsinalo {
    	protected $btCsinalo;
    	
    	public function setBtCsinalo(BtCsinalo $btCsinalo){
    		$this->btCsinalo = $btCsinalo;
    	}
    
    	public function csinaljAt($x, $y, $z){
    		$b = $this->btCsinalo->csinaljBt($y, $z);
    		return $x + $b;
    	}	
    }
    
    interface BtCsinalo {
    	public function csinaljBt($y, $z);
    }
    
    class BtKivonassalCsinalo implements BtCsinalo {
    	public function csinaljBt($y, $z){
    		return $y - $z;
    	}
    }
    


Általában ha globálisból rántasz be paramétereket az annak a jele, hogy hosszú hívási láncon kellene átadnod őket. Ez tipikusan igaz a konfigurációs paraméterekre, amiket a DI container szépen be tud injektálni alsó szintekre is anélkül, hogy hívási láncon kellene átküldenie őket.

  • struktúrált konfigurációs paraméter hívási láncos átadása
    
    $p = 1;
    a($p);
    
    function a($p){
    	b($p);
    }
    
    function b($p){
    	c($p);
    }
    
    function c($p){
    	d($p);
    }
    
    function d($p){
    	echo $p;
    }
    
  • struktúrált konfigurációs paraméter átadás globális névtér használatával
    
    $p = 1;
    a();
    
    function a(){
    	b();
    }
    
    function b(){
    	c();
    }
    
    function c(){
    	d();
    }
    
    function d(){
    	global $p;
    	echo $p;
    }
    
  • oo konfigurációs paraméter átadása container-en keresztül
    
    $p = 1;
    $container = new ABCDContainer($p);
    $a = $container->getA();
    $a->a();
    
    class ABCDContainer {
    	protected $p;
    	public function __construct($p){
    		$this->p = $p;
    	}
    
    	public function getA(){
    		return new A1($this->getB());
    	}
    	
    	public function getB(){
    		return new B1($this->getC());
    	}
    	
    	public function getC(){
    		return new C1($this->getD());
    	}
    	
    	public function getD(){
    		return new D1($this->p);
    	}
    }
    
    interface A {
    	public function a();
    }
    
    class A1 {
    	protected $b;
    	public function __construct(B $b){
    		this->b = $b;
    	}
    	
    	public function a(){
    		$this->b->b();
    	}
    }
    
    interface B {
    	public function b();
    }
    
    class B1 implements B {
    	protected $c;
    	public function __construct(C $c){
    		this->c = $c;
    	}
    	
    	public function b(){
    		$this->c->c();
    	}
    }
    
    interface C {
    	public function c();
    }
    
    class C1 implements C {
    	protected $d;
    	public function __construct(D $d){
    		this->d = $d;
    	}
    	
    	public function c(){
    		$this->d->d();
    	}
    }
    
    interface D {
    	public function d();
    }
    
    class D1 implements D {
    	protected $p;
    	public function __construct($p){
    		this->p = $p;
    	}
    
    	function d(){
    		echo $this->p;
    	}
    }
    
    
    


Ha jobban megnézed a struktúrált a(p) --> b(p) --> c(p) --> d(p) hívási láncot átalakítjuk new A(new B(new C(new D(p)))) kifejezéssé. Ezt az objektum gráf készítést meg szétosztjuk a container metódusai között, így szép átlátható kódot kapunk. Ha nem konfigurációs paraméterről van szó, akkor érdemes megvizsgálni, hogy tényleg a megfelelő helyen adsz e értéket neki. Elképzelhető, hogy a hívási láncon lejjebb lehet tenni, és akkor már nem kell annyiszor átadni. Lehet még realtime hívási láncot készíteni ugyanígy objektum gráf összeállítással, pl stream-ek pipe-olásánál, de ez nem jellemző megoldás a problémára. Nagyjából ezek a lehetőségek.
4

struktúrált, szorosan

Hidvégi Gábor · 2015. Júl. 22. (Sze), 17.36
struktúrált, szorosan csatolt
$a = csinaljAt(1, 2, 3);

function csinaljAt($x, $y, $z){
  $b = csinaljBt($y, $z);
  return $x + $b;
}

function csinaljBt($y, $z){
  return $y - $z;
}
Ez pont ugyanolyan lazán csatolt, mint a második példád, cserébe egyszerűbb.

Procedurális kód esetén mindig interface-re programozunk, azaz ha van egy modulunk modul_fuggveny1() és modul_fuggveny2() függvényekkel, akkor ez a felülete. PHP-ban például elég a függvényeket tartalmazó fájlt lecserélni, és meg is van oldva pl. a tesztelés:
//konfiguráció
$teszt = true;

//program
require_once($teszt ? 'modul_teszt.php' : 'modul.php');
Magyarul procedurális kód esetén a szoros csatolás és a DI problémaköre elvileg fel sem vetődik soha.
6

Szerintem félreértetted,

inf · 2015. Júl. 22. (Sze), 22.53
Szerintem félreértetted, azért írtam ennyit, hogy a kérdezőnek segítsek vele, nem azért, hogy válaszolj rá.
8

Magyarul procedurális kód

BlaZe · 2015. Júl. 22. (Sze), 23.20
Magyarul procedurális kód esetén a szoros csatolás és a DI problémaköre elvileg fel sem vetődik soha.
Types of coupling / Procedural programming. Btw az első hozzászólásodban az egyik legszorosabb csatolásra buzdítottad tóthikát, mert "azzal nincs baj".

Procedurális kód esetén mindig interface-re programozunk, azaz ha van egy modulunk modul_fuggveny1() és modul_fuggveny2() függvényekkel, akkor ez a felülete.
Az OOP szerinted nem pont ugyanezt csinálja? Csak kicsit gazdagabb ezt támogató eszközkészlettel, mint az include.
10

Az OOP önmagában nem véd meg

Hidvégi Gábor · 2015. Júl. 23. (Cs), 07.35
Az OOP önmagában nem véd meg attól, hogy a wikipédia linken található felsorolásban lévő dolgokat elkövesd. Például javascriptben a prototípus alapú öröklődést használva minden objektum tagja nyilvános.

Btw az első hozzászólásodban az egyik legszorosabb csatolásra buzdítottad tóthikát
Ez nem igaz. Ha egy olyan változót, aminek az értékét kívülről lekérik, több helyről is módosítják közvetlenül, akkor az rossz program, de ezt OOP-vel is könnyen meg lehet valósítani. Ilyet én nem mondtam neki, hogy ezt tegye, így a részedről ez egy gonosz megjegyzés volt.

Jó és rossz programot bármilyen paradigmával lehet írni, te is pontosan tudod, hogy a linux kernele, alaprendszere és a legtöbb hozzá tartozó rutinkönyvtár C-ben íródott, procedurálisan, de ugyanígy a biztonságot hirdető OpenBSD is.
9

Mérnöki megoldások

bamegakapa · 2015. Júl. 23. (Cs), 00.28
5

Köszönöm a válaszokat, úgy

tóthika · 2015. Júl. 22. (Sze), 19.35
Köszönöm a válaszokat, úgy gondolom, csak átállok OOP-ra.

A probléma nem a globális változókkal van, hanem ott, hogyha valamit szeretnék hozzáadni egy dinamikusan létrehozott formhoz, több helyen változtatni kellene a kódokon. Tudom, meg lehetne oldani strukturált programozással is, de nekem az már gányolásnak tűnik. Jobbnak igazából az osztályok tűnnek, lehetne 'absztrakt' függvénnyel is, de nem erre találták ki.
7

Ha az alapokkal tisztában

inf · 2015. Júl. 22. (Sze), 23.02
Ha az alapokkal tisztában vagy, akkor nézz körül Symfony és Laravel környékén, azokat mondják jónak PHP-ban. Érdemes átfutni a kódokat, hátha tanulsz belőlük valamit. (Én annak idején prototype.js-ből tanultam nagyon sokat. Ahogy nézem azóta teljesen megváltozott, és nem biztos, hogy előnyére.)
11

Nos, a következőt tudtam most

tóthika · 2015. Júl. 24. (P), 01.13
Nos, a következőt tudtam most létrehozni:

<?php

interface dbInt {
	public function Connect($database);
	public function Add();
	public function Get();
	public function Set();
}

class database implements dbInt {
	private $software = "mysql";//mysql, pgsql, mssql, etc...
	private $host = "127.0.0.1";
	private $name = "PHP";
	private $pass = "--pass--";
	private $database = "db_name";
	public function Connect($database)
	{
		$this->database = $database;
		$classname = $this->software . '_database';
		$functionname = $this->software . '_Connect';
		$connect = new $classname;
		$connect->$functionname($this->host, $this->name, $this->pass, $this->database);
	}
	public function Add()
	{
		//...
	}
	public function Get()
	{
		//...
	}
	public function Set()
	{
		//...
	}
}

?>
Egy egyszerű bővítőosztály leprogramozása után már igénybe is lehet venni az új adatbázist.
(Inf3rno ABContainer()-es példája)
A bővítőosztályoknak nem kell állapotuk lenniük, és könnyen kiterjeszthetjük egy másik adatbáziskezelőre is:


class mysql_database extends database {
	public function mysql_Connect($host, $name, $pass, $database)
	{
		return mysqli_connect(...);
	}
}

Hmm...
Milyen jó anyag ez :D
12

Azért nem ilyen egyszerű ez,

inf · 2015. Júl. 27. (H), 21.27
Azért nem ilyen egyszerű ez, de valahol mindenkinek el kell indulni vele. :-)

Szerintem elsőnek olvasd el a PSR-eket (jobb oldalt), és használd ezeket a konvenciókat a kódoláshoz. Pl nagybetűs osztálynevek, stb.

Másodiknak próbáld megérteni a SOLID principles-t. Leginkább az SRP-vel és a DIP-el foglalkozz.

Jelen esetben a database osztály Connect metódusa egyszerre példányosít leszármazott osztályokat, és csatlakozik adatbáziokhoz. Ez így zavaros, ha valaki ránézésre meg akarja érteni, hogy miről van szó. Márpedig általában csapatban fogsz fejleszteni, úgyhogy jó, ha ránézésre értik mások, hogy mit csinál a kódod. A másik, ami fontos, hogy nem érdemes string-ből változó vagy osztályneveket összefűzni. Nincs rá automatikus kiegészítés, könnyen elgépeled, és az IDE-k sem tudnak mit kezdeni vele egy refactoring-nál. Valami ilyesmi jobb lenne:

interface DatabaseInterface {
    public function connect();  
    public function add();  
    public function get();  
    public function set();  
}  

class MySQLDatabase implements DatabaseInterface {
    protected $host;  
    protected $database;  
    protected $username;  
    protected $password;  
	protected $link;
	
	public function __construct($database, $username = 'root', $password = '', $host = '127.0.0.1'){
		$this->database = $database;
		$this->username = $username;
		$this->password = $password;
		$this->host = $host;
	}
	
    public function connect()  
    {  
        $this->link = mysqli_connect($this->host, $this->username, $this->password, $this->database);  
    }  
    public function add()  
    {  
        //...  
    }  
    public function get()  
    {  
        //...  
    }  
    public function set()  
    {  
        //...  
    }  
}

class Container {
	protected $webshopCatalogDatabase;
	public function getWebshopCatalogDatabase(){
		if (!isset($this->webshopCatalogDatabase)){
			$this->webshopCatalogDatabase = new MySQLDatabase('webshop_catalog', 'php', '--pass--');
			$this->webshopCatalogDatabase->connect();
		}
		return $this->webshopCatalogDatabase;
	}
}

$container = new Container();
$webshopCatalogDatabase = $container->getWebshopCatalogDatabase();
$webshopCatalogDatabase->add();
Aztán ha adatbázist váltasz, akkor kicseréled a getWebshopCatalogDatabase()-ban a new MySQLDatabase()-t mondjuk new MongodbDatabase()-re, vagy ilyesmire. Feltéve, hogy az add(), set(), stb... ugyanazzal a DatabaseInterface-el leírható e mindkettőben. Több irány lehetséges. Pl a doctrine próbálja általánosan megcsinálni ugyanazt többféle adatbázisra. Ha nem akarsz ilyet, akkor csinálhatsz olyat, hogy kibővíted a MySQLDatabase osztályodat az egyes sql query-kkel, amiket használni fogsz, vagy csinálhatsz adaptereket, amik a MySQLDatabase példányt használják és csak a query-ket állítják össze. Használhatsz PDO-t is mysqli helyett, mert a DatabaseInterface elfedi az implementációt.

Szerintem a próbálkozásaid mellett éredemes minél többet olvasni arról, hogy mások hogyan használják az oop-t, mert gyorsan sokat lehet tanulni belőle. Ha nem olvasol, hanem mindenre magadtól akarsz rájönni, akkor az sokszor annyi idő. Ugyanígy, ha keretrendszert sajátot írsz, ahelyett, hogy a meglévőket tanulnád meg használni, az is sokszor annyi idő, és így tovább... Szerintem próbáld ki a doctrine-t, aztán ha beletanultál, akkor máris van egy olyan tudásod, ami egy állásinterjún jó pontnak számít.