ugrás a tartalomhoz

Robert C. Martin - Clean Architecture and Design

MadBence · 2014. Feb. 26. (Sze), 19.51
Robert C. Martin előadása a helyes architektúra-tervezésről
 
1

Diák

complex857 · 2014. Feb. 27. (Cs), 15.49
2

Köszi, ez már használhatóbb,

inf3rno · 2014. Feb. 27. (Cs), 20.38
Köszi, ez már használhatóbb, diák nélkül elég gáz...
3

Én pont ugyanerre a

inf3rno · 2014. Már. 3. (H), 02.47
Én pont ugyanerre a problémára kerestem megoldást. Nekem a REST API-m mellé kell betennem egy sima webes felületet, és mindenáron úgy akartam, hogy a webes felület egy REST kliens legyen, és a REST API-tól kérje el az adatokat. Baromi nagy overengineering lett volna mindent oda-vissza alakítani HTTP kérésekre... Sokkal jobb ez így, hogy a REST API-t leválasztjuk az alkalmazásról, és létrehozunk mellé egy másik delivery method-ot. Egyedül az a kínom vele, hogy nem nekem jutott eszembe... :D

Egyébként az egész szinte ugyanaz, mint az n-tier architecture, csak más elnevezésekkel. Nem igazán találtam semmi különbséget, talán annyit, hogy minden action-t külön interactor osztályba akarnak szervezni. Én nem hiszem, hogy így fogom csinálni, mert hamar el lehet szállni a fájlok számával... Találtam néhány példát a neten, tanulmányozni fogom őket, hátha még okosodok.

Köszi a blogmarkot!
4

Jobban belenéztem,

inf3rno · 2014. Már. 9. (V), 02.51
Jobban belenéztem, helyesbítek, egyáltalán nem olyan, mint az n-tier. Sokkal jobb. A clean architecture az alkalmazás fejlesztésre koncentrál, az n-tier, meg az összes többi, amit általában használunk meg arra, hogy milyen külső keretrendszereket rántunk be. A clean-nél ez harmadrangú dolog, mert minden külső keretrendszerhez van csatoló réteg. Ez valamennyi plusz munkát jelent, de megéri a befektetés, mert tesztelhetőbb és könnyebben fejleszthető/karbantartható lesz tőle a rendszer.
5

Remélem nem haragudtok meg,

szjanihu · 2014. Már. 11. (K), 20.30
Remélem nem haragudtok meg, ha belinkelem az egyik írásomat, amelyben ezt a témát boncolgattam DDD kontextusban.
6

Np.Én most próbálgatom,

inf3rno · 2014. Már. 12. (Sze), 00.18
Np.

Én most próbálgatom, hogy a gyakorlatban hogyan lehetne szétválasztani az alkalmazás egyes részeit, de elég komplikált. Általában az ember úgy állna neki ilyesminek, hogy csinálni egy container-t, és azon keresztül érik el egymást a service-ek, vagy nevezhetjük akár modulnak is őket, tök mindegy. Ez a fajta megközelítés szerintem a clean architecture-nél nem működik, vagy legalábbis mindenképp többletinformáció kell minden egyes service-ről, nem elég csak egy név. Kell a kapcsolódó interface, kell, hogy kérést küld, vagy fogad, stb... Én jelenleg úgy vagyok vele, hogy a bootstrap-ben kézzel szerelem össze a függőségeket nagyobb modulokból, vagy esetleg factory-kat regisztrálok be az összeszereléshez. Még nagyon alfa az elképzelésem erről... :-)

Első körben valami ilyesmit képzeltem el:


interface BusinessLogicService {
	//business logic interface desc
}

interface StorageService {
	//storage interface desc
}


/** @Dependency(BusinessLogicService) */
class RestApi {
	// rest api implementation
}

/** @Dependency(StorageService) */
class Application implements BusinessLogicService {
	// business logic implementation
}

class MySQLStorage implements StorageService {
	// mysql database storage implementation
}

$storage = new MySQLStorage();
$app = new Application();
$restApi = new RestApi();

$dependencyResolver = new DependencyResolver();
$dependencyResolver->registerService($storage);
$dependencyResolver->registerService($app);
$dependencyResolver->registerService($restApi);
$dependencyResolver->injectAllDependency();

$restApi->run();

Ha jobban belegondolok le lehet szűkíteni sima függőség kezelésre a problémámat. De sajnos nem ilyen egyszerű. A dependency szó nem elég kifejező arra nézve, hogy milyen irányban mennek a kérések, és milyen irányban a válaszok.

Érdekes megoldás a komponens kezelős is. Amire szükség van: legyen egy közös interface egy adott típusú kérésre, amit a kérés küldője használ a fogadója meg implementál. Legyen egy registry, ami automatikusan csatlakoztatja interface szerint az egyes service-eket. Kell még egy container, ami interface szerint tud példányosítani, viszont akkor annak a metódusait szintén annotálni kell, hogy milyen típust adnak vissza, hogy előre lehessen tudni melyik metódust hívja meg a registry, ha egy-egy service-t példányosítani kell egy függőség miatt. Összességében mindez automatizáltan nem biztos, hogy megoldható mondjuk egy composerrel (bár nem ismerem teljes mértékben annak a korlátait). Pl ha olyat akarok, hogy teszt környezetben teljes mértékben ki legyenek mockolva a service-ek, akkor elég csak kicserélni a container-t... A komponens kezelőknél meg úgy sejtem, hogy hardcodolni kellene a modulokba az ilyen eltéréseket.

Esetleg még lehetne service locator mintával próbálkozni, de azzal elég nehezen lehet követni a függőségeket. Jobb kitenni setter injection-be inkább, és elvárni, hogy megadják őket. Kevés modulnál szinte tökmindegy, hogy kézzel rakjuk össze, vagy valami automatizált rendszerre bízzuk, sok kicsi modulnál vannak inkább a problémák.

Amit nehezen dolgozok fel, hogy clean architecture-ben a business logic semmit nem szabad, hogy tudjon a külvilágról, mégis valahogy bele kell injektálni pl az adatbázis eléréssel kapcsolatos service-t, vagy legalábbis hozzá kell csatolni valamilyen kimenő csatolóhoz. Ha nem kérés-válasz alapú dologról lenne szó, akkor kapásból esemény kezeléssel oldanám meg ezt az egészet. Így viszont csak a dependency injection marad. A service locator beinjektálása szerintem nem vezet semmi jóra, mert ha laza csatolást akarunk, akkor a service neve az, amiről aztán végképp nem kell tudnia a business logicnak. Bárhogy is agyalok, mindenképp lesz valamilyen megkötés az injektálás kapcsán a business logicban, kivéve két esetet, az egyik a kézi összerakása a függőségeknek, a másik meg a setter metódusok felannotálása, hogy tudja a rendszer, hogy külső függőséget várnak egy bizonyos típusú interface-el. folyt köv...
7

Elképzelhető, hogy

szjanihu · 2014. Már. 12. (Sze), 09.45
Elképzelhető, hogy félreértettelek (többször megtörtént, úgy látszik nehezen fogom fel a gondolatmeneted), de válaszolok, hátha eltalálom.

Ha az alkalmazáshoz akarsz írni egy REST API-t, az a presentation rétegbe való csakúgy, mint mondjuk egy WEB API. Mivel mindkettő HTTP kommunikációra indul, ugyanúgy használhatsz tetszőleges MVC frameworköt. Ha egy másik PHP-s alkalmazásnak akarsz hozzáférést biztosítani az alkalmazáshoz REST-en keresztül, akkor lényegében egy PHP-s API-t akarsz csinálni, ami mögött REST kommunikáció van. Ezt úgy teheted meg, hogy csinálsz egy API modult, ami tartalmazza az összes interfészt és DTO-t, amit használni akarsz a hívó kódban; és ehhez csinálsz egy implementációs modult, ami elintézi a REST hívásokat és a hívó kóddal az API-ban (contract) lévő interfészeken keresztül, a szintén ott lévő DTO objektumokkal kommunikál. Mivel a hívó kód forrás szinten csak a contractban lévő dolgokat használja, bármikor kicserélhető a mögötte lévő API implementáció. Mind a hívó, mind az implementációs modul az API modultól függ; előbbihez nyilván fel kell venni mondjuk composerrel az utóbbit függőségként, hogy működjön az egész, de kód szinten teljesen el van szeparálva a kettő.

A másik dolog, a perzisztencia. Nem véletlenül mentem el DDD irányba :). A domain réteg, ami a business logicot tartalmazza, legyen teljesen független minden más rétegtől. Ez nem azt jelenti, hogy önmagában működőképes; hanem azt, hogy kód szinten nincs külső függősége. A repository patternt alkalmazva magát az interfészt a domain rétegbe rakod, míg az implementációt az infrastructure rétegbe. Ahhoz, hogy működjön az egész, egy DIC-ben persze össze kell gyúrnod az egészet.
9

Tökmindegy, hogy mik a

inf3rno · 2014. Már. 12. (Sze), 10.57
Tökmindegy, hogy mik a rétegek nevei, kb így fog kinézni a helyzet egy alkalmazásnál, aminek csak egy REST API-ja:

⥂ {REST API} ⥂ {Business Logic} ⥂ {MySQL adapter}
⇄⇆⇅⇵⥂⥄⥃←↑→↓↖↗↘↙

Mint látható a kérés-válasz alapú megoldásoknál csak egyféle kapcsolat létezik: , ami szinkron. A websocket-es megoldásokat is le lehet írni hasonlóan, csak ott a két nyíl és többé-kevésbé egymástól függetlenül, aszinkron van jelen.

Adjunk hozzá dependency injection-t:

⥂ {REST API} ⥂ {Business Logic} ⥂ {MySQL adapter}

           ↖            ↑ 

             {Service Provider}
Ugye a REST API a Business Logic felé intéz kéréseket, a Business Logic meg a MySQL adapter felé. Mindkettő tehát függ attól, hogy a Service Provider mit injektál be nekik.

Itt van egy olyan paradoxon, hogyha a Service Provider saját magát adja át, és a REST API és a Business Logic őt használja a függőségek elkérésére, akkor a Service Provider maga is függőséggé válik, és kell neki egy interface, amit a REST API-nak és a Business Logic-nak ismernie kell. Úgy vagyok vele, hogy ezt jobb szeretném elkerülni, szóval nem szeretném, hogy pl a Business Logic tudjon a Service Provider-ről.
11

Alakul a molekula:namespace

inf3rno · 2014. Már. 12. (Sze), 11.14
Alakul a molekula:

namespace Contract {
	interface Application {}
	interface EntryPoint {}
}

namespace RestApi {

	class Module {
		/** @return Contract\EntryPoint */
		public function getEntryPoint(Contract\Application $application){}
	}
	
	class EntryPoint implements Contract\EntryPoint {}
}

namespace BusinessLogic {

	class Module {
		/** @return Contract\Application */
		public function getApplication(){}
	}
	
	class Application implements Contract\Application {}
}

$dependencyResolver = new DependencyResolver();
$dependencyResolver->parse(new RestApi\Module());
$dependencyResolver->parse(new BusinessLogic\Module());
$dependencyResolver->invoke(function (Contract\EntryPoint $entryPoint){
	$entrypoint->handleRequest();
});

Egyelőre ezt még továbbfejlesztem, lazy load, meg circular dependency, amit még nem igazán támogat, de első körben elég jónak tűnik...

Gyakorlatilag elég hasonló, mint amit requirejs vagy nodejs csinál egy-egy modul betöltésénél. A lazy load-hoz szükség van a dependency resolver elérésére legalább a Module osztályokban valahol...

Lazy load lehetséges megoldása:

	class Module {
		/** @return Contract\EntryPoint */
		public function getEntryPoint(){
			if (blah)
				$application = $this->dependencyResolver->invoke(function (Contract\Application $application){
					return $application;
				});
		}
	}
Ez egy elviselhető kompromisszum, ha a dependency resolver megáll a module-ban, és nem kerül be mélyebb rétegekbe az invoke...

Nem tudom ilyesmi lehetséges e a composer-ben, de erősen kétlem, amennyire én tudom az csak autoload-ot biztosít minden modulnak, aztán kalap... Szóval kézzel kell példányosítani a modulon belül, vagy injektálni kell a függőségeket... Nem feltétlenül egyezik a két eszköz célja... Pl a REST API-nál simán berántanék autoload-dal bármilyen HTTP kérés feldolgozó keretrendszert...

Na szóval összefoglalva valami olyasmit akarok tákolni, amivel környezet függően könnyen össze lehet állítani egy modul listát, és a függőségeket automatikusan lekezeli a rendszer. Mindemellett lazy load-ot használ, szóval ha egy függőségre nincs szükség, akkor nem tölti be azt. Mindezt úgy, hogy a függőségeket kézzel könnyen be lehessen injektálni vagy kimockolni factory-k vagy bármi hasonlók megadása nélkül, mintha csak egy kézzel összerakott rendszer lenne...
12

Ja szóval nem értjük egymást.

inf3rno · 2014. Már. 12. (Sze), 11.15
Ja szóval biztos, hogy nem értjük egymást. Én még azzal sem vagyok teljesen tisztában, hogy mit akarok, egyelőre azon agyalok... :D

szerk:

Nagyjából megvan...

Olyan rendszert képzeltem el, amiben vannak modulok, amikhez egy csomó apróbb osztály tartozik. Tegyük fel, hogy kiválasztunk egy modulból egy ilyen apróbb osztályt. Ez az osztály függhet a modulban lévő többi osztály példányaitól, illetve függhet egy másik modulban lévő osztályok példányaitól. Nevezzük ezeket objektumoknak. Azt szeretném, ha az egyik modulban lévő osztály nem tudna semmit arról, hogy egyáltalán létezik olyan, hogy másik modul. Mitovább azt szeretném, ha maga a modul se tudna róla, hogy létezik olyan, hogy másik modul, csak azt tudná, hogy neki vannak függőségei, amiket valahonnan kap. Ezt úgy lehet elérni, hogy megadok minden függőséghez egy-egy interface-t, és ezzel az interface-el rendelkező objektum beinjektálását várom el. Így az egyes modulok között létrejön egy-egy határ réteg, ami mentén azok elválaszthatóak egymástól. Minden modulnál megvan, hogy milyen felülető objektumokra van szüksége kívülről, és milyen felülető objektumokat biztosít más moduloknak. A modulok közötti injektálást tehát ki kell szervezni egy olyan osztálynak, ami már tudhat a modulok létezéséről, mert egy absztrakciós szinttel feljebb van. Mikor jó ez a megközelítés? Ha modul szintű teszteket akarunk végrehajtani, és ki kell mockolni az egyes modulokat, esetleg újra akarjuk használni a modul szintű DI containert a tesztjeinkben, és nem akarunk egy-egy osztályt kézzel példányosítani (gyakorlatilag a unit tesztjeink ellenkező esetben a DI container kódjának a másolataival lennének teli), illetve ha többszáz apróbb osztály függ egymástól keresztbe kasul, és több eltérő környezetben elég nehézkes lenne eltérő ezer soros config fájlokat csinálni hozzá egyetlen DI container-hez...

Jelenleg próbálok kidolgozni egy felületet, amin keresztül nem kell tudnia az egyes moduloknak az egy absztrakciós szinttel fentebbi dolgokról: a függőség kezelőről és a többi modulról. Nem igazán egyszerű, mert a lazy load-nál muszáj factory-kat vagy valami hasonló beinjektálni... A másik, amire mindenképp szükség van az hogy reflectionnel végig kell menni az egyes modulok felületén, és kiolvasni az annotációkat, hogy tudjam mit kell kívülről injektálni, és mit nem... Hát nem egyszerű...
13

Így az egyes modulok között

szjanihu · 2014. Már. 12. (Sze), 19.28
Így az egyes modulok között létrejön egy-egy határ réteg, ami mentén azok elválaszthatóak egymástól.

Így van, ez maga a contract, az API, amelyek keresztül a két modul kommunikál. Azonban nem kell minden modulhoz ilyet készíteni. Ennek akkor van szerepe, ha a két modul eltérő rétegben van. Ha egy jól körülhatárolható modulhalmaz együtt dolgozik és egy jól meghatározott helyen csatlakozik másik modulhalmazhoz, akkor a halmazon belül nincs szükség ilyen határfelületekre. Ez a fajta particionálás szerintem megfelel a gráfelméleti erőse összefüggő komponensek elméletével, de ebben nem vagyok biztos.

Teszteléssel kapcsolatban: a unit tesztekben nem kell DIC, mivel azért unit teszt a neve :) Integrációs tesztekben pedig megoldható, hogy a DIC-et más konfig fájl(ok)ból konfigurálod. Ez gyakran kimerül annyiban, hogy pusztán más konfig adatokkal (URL, név, jelszó, stb.) etetem meg az osztályokat, vagyis az ezeket tartalmazó fájlt kell csak duplikálnom és átírni a paramétereket a tesztben használandókra (Spring-ben ez pl. teljesen el van szeparálva, lévén a paraméterek .properties fájlokban vannak, míg a config maga .xml-ben).

Egy normális DIC azért támogatja azt, hogy szétszórt, modul szintű config fájlokat adj meg neki, amiből összeszedi, amit kell. Plusz ugye ha annotációkat is támogat, akkor még kevesebbet kell írni (ez viszont már ízlés/helyzet függő).
14

Teszteléssel kapcsolatban: a

inf3rno · 2014. Már. 13. (Cs), 08.00
Teszteléssel kapcsolatban: a unit tesztekben nem kell DIC, mivel azért unit teszt a neve :) Integrációs tesztekben pedig megoldható, hogy a DIC-et más konfig fájl(ok)ból konfigurálod. Ez gyakran kimerül annyiban, hogy pusztán más konfig adatokkal (URL, név, jelszó, stb.) etetem meg az osztályokat, vagyis az ezeket tartalmazó fájlt kell csak duplikálnom és átírni a paramétereket a tesztben használandókra (Spring-ben ez pl. teljesen el van szeparálva, lévén a paraméterek .properties fájlokban vannak, míg a config maga .xml-ben).


Ezzel nem feltétlen értek egyet, szerintem semmi értelme leduplikálni a DIC logikáját, és úgy példányosítani. A unit tesztek arra fókuszálnak, hogy mi van az osztály felületén, nem arra, hogy hogyan kell példányosítani azt, ez utóbbi inkább a setup-ban szokott helyet kapni. A példányosítás egyébként is a DIC feladata, és nem az osztályé... Na mondjuk ez a fajta megközelítés nekem is új, még tesztelnem kell élesben. A DIC-nél szeretnék csinálni egy MockDIC-et, aminél kimockolom az osztály összes függőségét, és azt használnám fel unit test-ekre. Így nem kéne állandóan lemásolni a példányosítás logikáját, és könnyebben karbantarthatóak lennének a tesztjeim. Persze a példányosítást is lehet tesztelni, abban az esetben viszont a DIC-re tolnék unit tesztet, mert ő végzi... Annyi hézag van ebben az elméletben, hogy a konstruktort is tesztelni kéne valahogy, és nem tiszta, hogy a DIC-vel, vagy az osztály metódusaival együtt... Csinálok egy példát ilyesmire.
15

A unit tesztes résznél arra

szjanihu · 2014. Már. 13. (Cs), 09.52
A unit tesztes résznél arra utaltam, hogy ott a lehető legtöbb függőséget kimockolja az ember, vagyis csak az épp tesztelendő osztályt kell példányosítani. Ezért unit teszt, mert egy egységet (osztályt, azon belül egy/néhány metódust tesztel). Ha már DIC-et használt tesztben, az nem unit teszt, még akkor sem, ha a PHPUnit_Framework_TestCase osztályból származtatod :)
16

A unit tesztes résznél arra

inf3rno · 2014. Már. 13. (Cs), 11.10
A unit tesztes résznél arra utaltam, hogy ott a lehető legtöbb függőséget kimockolja az ember, vagyis csak az épp tesztelendő osztályt kell példányosítani. Ezért unit teszt, mert egy egységet (osztályt, azon belül egy/néhány metódust tesztel). Ha már DIC-et használt tesztben, az nem unit teszt, még akkor sem, ha a PHPUnit_Framework_TestCase osztályból származtatod :)


Nem értem, miért?

Én még nem találkoztam olyan szabállyal, ami előírta volna, hogy egy-egy metódus tesztelésénél hogyan példányosítsam az adott osztályt... Nem a példányosítást teszteljük a unit test-ben, hanem az osztály valamelyik metódusát... A DIC-et is simán be lehet állítani, hogy kimockolja az osztály függőségeit, felesleges mindezt kézzel csinálni és copy-paste-elni a példányosítást a DIC-ből a unit test-be...

class MyTest extends blah_TestCase {
	
	protected $my;
	protected $myDep;
	
	public function setUp(){
		$mockDic = blah_mock_dic('\My\DIC');
		$mockDic->useOriginal('getMy');
		$this->my = $mockDic->getMy();
		$this->myDep = $mockDic->getADependencyOfMy();
	}
	
	public function testMyDoSomethingCallsADependency(){
		$this->my->doSomething();
		$this->myDep->assertHaveBeenCalled('doSomethingForMySomething')
	}
	
}
Jó, ez nem phpUnit, de most nem volt lelkierőm működő kódot írni, a lényeg szerintem átjön... Nyilván még hozzá kell csapni egy helper-t, ami beállítja a mock dic-et, hogy mock ojjektumokat adjon vissza a függőségek helyett.
17

Ideális esetben csak azt az

szjanihu · 2014. Már. 13. (Cs), 12.19
Ideális esetben csak azt az osztályt példányosítjuk tesztben, amit tesztelünk, minden mást mockolunk. Stateful függőségeknél ettől valamennyire eltérhetünk, lévén azokat a tesztelt kód is létrehozhatja.

A mockokat a használt xunit keretrendszerrel létrehozhatod helyben ($this->getMock(...)), semmi szükség DIC-re. Ha DIC-et használsz, akkor eleve nem használsz mockokat, ez pedig nem unit teszt, hanem integrációs teszt. Erre is szükség van és fontos, de nem helyettesíti a unit tesztet.
18

A mockokat a használt xunit

inf3rno · 2014. Már. 13. (Cs), 13.06
A mockokat a használt xunit keretrendszerrel létrehozhatod helyben ($this->getMock(...)), semmi szükség DIC-re. Ha DIC-et használsz, akkor eleve nem használsz mockokat, ez pedig nem unit teszt, hanem integrációs teszt. Erre is szükség van és fontos, de nem helyettesíti a unit tesztet.


Részben egyetértek, részben nem.

A példakód látom nem jött át. A példában a mock DIC nálam egy speciális osztály, amit az eredeti DIC alapján csináltam, és ami ugyanazt a logikát követi példányosításkor, mint az alap DIC, azzal a kivétellel, hogy a külső függőségeket kimockolja az osztályból. Gyakorlatilag ugyanazt csinálja, mint amit te kézzel szoktál, amikor példányosítasz és mockolsz. A unit test-ek célja egy-egy metódus tesztelése az osztályon, és a unit test-eknek teljesen mindegy, hogy a példányosítást kézzel csinálod vagy automatizáltan. Ha kézzel csinálod, akkor nem lesz DRY a kódot, mert ugyanazt már egyszer leírtad a DIC-ben... Ha ez neked problémát jelent, akkor azt tanácsolom, hogy ne használd a Mock osztályokat sem, mert akkor azok is integrációs tesztnek számítanak ennyi erővel...

Ahhoz, hogy ezt a DIC alapján működő mockolós megközelítést használni lehessen szerintem szükség van bizonyos konvenciók betartására a DIC-nél és a konstruktor írásánál. Elméletileg lehetséges újrahasznosítani a DIC kódját a tesztekben, a gyakorlatban meg majd elválik. Egyelőre még ki kell dolgoznom egy működő példát, hogy teszteljem az ötletet...
19

Szóval interfész mockolásához

szjanihu · 2014. Már. 13. (Cs), 13.49
Szóval interfész mockolásához egy DIC-ből kéred le a mockot ahelyett, hogy hívnál egy $this->getMock('Interfacename')-et. Ezzel nem spórolsz semmit, mert ugyanúgy 1 sort írsz le, viszont DIC framework cserénél majd fogod a fejed.

Amit te próbálsz elérni, azt normális mock frameworkökben (Mockito) megoldják pl. annotációkkal (Shorthand for mocks creation - @Mock annotation), ha már az ember lusta leírni azt az egy sort.

A DIC nem más, mint konfiguráció, ami lehet PHP-ben, XML-ben, annotácókkal megoldva, vagy akárhogy, de unit tesztben semmi keresnivalója.

Azt viszont tényleg nem értem, hogy tudod mock objektumok előállítására használni ugyanazt a DIC konfigurációt, mint amit production környezetben használsz? Utóbbi pont, hogy igazi objektumokat ad vissza.
20

Általában egy DIC valahogy

inf3rno · 2014. Már. 13. (Cs), 15.09
Általában egy DIC valahogy így épül fel. (Most tekintsünk el a DIC keretrendszeres általános megoldásoktól).

class DIC {
	protected $a;
	protected $b;

	/** @returns A */
	public function getA(){
		if (!isset($this->a)
			$this->a = new A();
		return $this->a;
	}
	
	/** @returns B */
	public function getB(){
		if (!isset($this->b))
		{
			$this->b = new B();
			$this->b->setA($this->getA());
		}
		return $this->b;
	}
}
Ha egy B-re van szükségem a unit test-hez, akkor kimockolom a getA()-t, és beteszek helyette egy fake-et, ami egy mockolt A-t ad. Ezt lehet automatizálni, a phpdoc kiolvasásával.

Az igaz, hogy a tesztek így szorosan csatoltak lesznek ehhez a fajta DIC megvalósításhoz... Szóval ja, nem lesz cserélhető. Egyébként szerintem az általános jellegű DIC-knél is lehetne ilyen kiegészítőt írni, ami segíti a unit tesztelést. Ez van, mindennek ára van... Ami szintén problémás lehet az a konstruktorok tesztelése.

szerk:

Bár automatizálható, de nem éri meg az erőfeszítést. Be kell parsolni hozzá az összes felannotált php fájlt, megnézni benne a use statement-eket és a névteret, aztán az alapján egy map-et csinálni, hogy az annotációkban melyik osztály mit jelent. Sajnos a php csak ennyire képes annotációk terén. Elméletileg a doctrine annotations-ben van olyan parser, ami ezt meg tudja csinálni. Ami nem tetszik, hogy minden módosítás után le kell tolni egy build-et ahhoz, hogy unit test-eket lehessen futtatni... Talán ha watcher-re bindelném a build-et, akkor használható lenne ez a megoldás... Lényeg a lényeg, össze lehetne tákolni valahogy de nem lenne szép. Hmm ahogy néztem doctrine annotations-ben is úgy oldják meg, hogy külön autoloader kell az annotált osztályoknak, hogy tudja, hogy melyik fájlból jönnek és onnan a use statement-eket fel tudja dolgozni a rendszer. Szóval az autoloader átírásával futásidőben is megy a dolog... Ettől még persze ugyanolyan gányolt workaround marad. Kicsit arra emlékeztet, amibe javascriptben régebben szoktam belefutni, amikor böngészők közötti különbségeket kellett valahogy eltüntetni..
21

Átgondoltam újra, azt hiszem

inf3rno · 2014. Már. 16. (V), 10.32
Átgondoltam újra, azt hiszem igazad lehet ezzel kapcsolatban. Később csinálok azért példákat rá kíváncsiságból...
22

Ok. Ha jutsz valamire, akár

szjanihu · 2014. Már. 16. (V), 22.09
Ok. Ha jutsz valamire, akár így, akár úgy, oszd meg, kíváncsi vagyok!
8

MVC

Hidvégi Gábor · 2014. Már. 12. (Sze), 09.57
Valahogy erre állt rá mindjárt a tekintetem, szerintem jól látod:
Az MVC patternt sokan hibásan projekt szinten alkalmazzák, átszőve azzal a teljes kódbázist, holott ez a presentation rétegbe való. Ebből is következik, hogy az MVC frameworknek nincs keresnivalója alacsonyabb szinten. Érdemes akár külön modulba rakni a presentation réteget, teljesen leválasztva azt a többitől. Ez olyan előnyökkel jár, mint például az, hogy bármikor lecserélhetjük azt (mondjuk másik MVC frameworköt felhasználva), vagy könnyen készíthetünk egy másik interfészt (SOAP, REST, stb.).
10

Ja ez igaz.

inf3rno · 2014. Már. 12. (Sze), 10.58
Ja ez igaz.