ugrás a tartalomhoz

Tervezési minták

webproghu · 2013. Már. 1. (P), 21.53
Sziasztok!

Alapvetően a tervezési mintákkal úgy vagyok, hogy ismerek párat, de van még jó sok, amit nem, és szívesen megismerkednék velük. Tudom, hogy van pár könyv a témában, illetve az interneten is fellelhető sok leírás, példa róluk, de (nem tudom más hogy van vele), nekem ezek nagyrészt homályosak, illetve sok esetben ellent is mondanak egymásnak.
Tehát ezzel a topiccal az lenne a célom, hogy kitárgyaljuk mi mire is való, milyen módon kell implementálni, esetleg ha ebben nálam okosabb emberek megosztanák a tapasztalataikat, azzal elősegítenék a mások, illetve saját magam okulását is.
 
1

Konkrét kérdésre lehet

inf · 2013. Már. 2. (Szo), 01.42
Konkrét kérdésre lehet konkrét választ adni, itt meg egy kérdőjelet sem látok. :-)
2

Rossz minta

Pepita · 2013. Már. 2. (Szo), 02.09
Van egy nagyon rossz "tervezési minta", amit sajnos nagyon sokan követnek:
"Nem tervezünk semmit, valahogy összelapátolunk valamit, majd kimagyarázzuk az ügyfélnél, úgyse ide fog visszajönni."

Ezzel akkor van baj, ha az ügyfél mégis visszajön...

Bocs a negatív példáért, de erre a témanyitóra ez jutott eszembe. Szerintem jobb lenne, ha előbb te bemutatnál párat, amit ismersz, hátha kedvet kap tőle más is.
3

ezek nagyrészt homályosak,

MadBence · 2013. Már. 2. (Szo), 03.21
ezek nagyrészt homályosak, illetve sok esetben ellent is mondanak egymásnak.

Ezek a minták best practice-ok, azaz sikeresnek bizonyultak számos probléma hatékony megoldásában. Nem szentírás, fel kell ismerni, mikor lehet/érdemes egyiket/másikat használni.
Legyegyszerűbb példa a Singleton tervezési minta. Bizonyos problémákra hatékony és egyszerű megoldást nyújt, ugyanakkor megvannak a maga hátrányai (tesztelés macerás, stb). Néha ezek a hátrányok annyira hangsúlyosak (pl annyira jó fejek vagyunk, hogy kifejezetten sokat szeretnénk tesztelni), hogy az általa nyújtott előnyök már nem adnak annyi pluszt, mint szeretnénk.

Tehát a mintákat nem ész nélkül kell használni, illik azt a néhányat tudni, és felismerni, hogy "hohó, minek gondolkozok én egy jó megoldáson, amikor erről a problémáról süt, hogy a Proxy tök jól működne!".

Végszó: There is no silver bullet
4

Hát ja kb erről szól a dolog,

inf · 2013. Már. 2. (Szo), 03.35
Hát ja kb erről szól a dolog, van a design patterns nevű könyv, abban elég részletesen le van írva, hogy melyik mintát mikor szokás használni, de ha ez nem lenne elég, akkor refactoring by patterns, és még egy csomó másik könyv még több példát hoznak a témában. A netes cikkek minősége elég bizonytalan, szóval én nem feltétlen alapoznék rájuk. Azt mondják dzone-on érdemes cikkeket olvasni, de ott sem mindig valami magas a színvonal. Én ha valami nem megy, akkor inkább stackoverflow-on rákeresek, megnézem, hogy mennyi +1-et kapott egy hozzászólás, mert az elég jól jelzi, hogy mennyire hiteles...

Az adapter-t kimondottan szeretem, legalábbis elméletben minden olyan esetben használni kéne, amikor egy külső rendszert csatolsz a sajátodhoz. Ez azért van így, mert ha kicseréled a külső rendszert, akkor elég csak az adaptert kicserélned, nem kell könyékig túrnod a kódban. Ugyanígy verzió váltásnál ha az adapter tesztjei zöldek, akkor nagy valószínűséggel nem lesz probléma. Szóval ez laza csatoláshoz elég jó választás.

Van az observer, ami szintén laza csatoláshoz jó, javascriptben elég sűrűn használja az ember.

Van még abstract factory, factory method, builder, stb... ezekkel meg tudod azt csinálni, hogy lesz egy külön osztályod, ami felkonfigolja az objektumokat, és nem kell neked kézzel megcsinálnod, ha szükséged van egy-egy objektumra. A másik, ami fontos, hogy a factory dönti el, hogy milyen osztályt példányosít, neked csak annyi a lényeg, hogy egy adott interface-t megvalósítson ez a példány.

Van még a decorator, ami nekem személy szerint nem jön be annyira.

Aztán van még vagy három tucat, de nyilván nem fogom mindet felsorolni, nem is hiszem, hogy eszembe jutnának...
5

Elnevezések

T.G · 2013. Már. 2. (Szo), 08.16
Kezdő programozóként nem is feltétlenül az az elvárás, hogy a saját feladatodban lásd meg, hogy milyen mintákat lehetne felhasználni. Hanem az, hogy amikor mások kódját olvasod és egy osztálynak AkármiObserver, AkármiAdapter vagy AkármiIterator a neve, akkor tud, hogy ezek mit csinálnak.
Magam részéről a tervezési minták nagy hasznosságát ebbe látom. Egy-egy jól elnevezett osztály neve annyi plusz információt ad, hogy meg sem kell nézni az osztály forrását, mégis mindent tudsz róla.
Amint elkezdesz keretrendszereket használni, látni fogod, hogy ez sokat segít a rendszer megismerésében.
6

Teljesen jogos a felvetés,

webproghu · 2013. Már. 2. (Szo), 12.04
Teljesen jogos a felvetés, hogy nem tettem fel kérdést a topic indításánál, ezért most meg is teszem. Kezdjük mondjuk a singletonnal, ez nagyjából a legegyszerűbb minta, az objektum egyszer példányosítja magát, majd minden alkalommal ugyanazt a példányt adja vissza:

class Singleton {
private static $instance;

private function __construct() {
}

private function __clone() {
}

private function __wakeup() {
}

public static function getInstance() {
if (!self::$instance) {
self::$instance = new User();
}

return self::$instance;
}
}

Ez eddig teljesen tiszta is. Factory-nál viszont nem egészen értem, mi az abstract factory és a factory method közötti különbség. A problémám főképp az, hogy könyvekben, tutorialokban néha-néha ellentmondanak egymásnak a példák, és nem tudom melyiknek is kéne hinni.

A Factory method elvileg valami ilyesmi lenne:

class FirstProduct {
}

class SecondProduct {
}

class ProductFactory {
public static function factory($type) {
$product = ucfirst($type).'Product';
if (class_exists($product)) {
return new $product();
} else {
throw new Exception('Invalid product');
}
}
}

Ez egy gyártó minta, elvileg ugyanazzal a felülettel, de más konkrét implementációval rendelkező objektumok gyártására szolgál.
Az Abstract Factory-t valaki el tudná magyarázni, hogy én is megértsem? :)
7

Közben nézegettem az Abstract

webproghu · 2013. Már. 2. (Szo), 12.50
Közben nézegettem az Abstract Factory-t több helyen. Az én értelmezésemben két különbség van közötte és a Factory Method között:
- először is, az abstract factory definiál egy abstract osztályt, amely megköveteli a gyártandó objektumok legyártását végrehajtó metódust
- abstract factory esetében nem egy általános gyártó metódus van, hanem minden objektum legyártásához külön metódust követel meg

Tehát valami ilyesmi:

interface Product {
}

class FirstProduct implements Product {
}

class SecondProduct implements Product {
}

// és itt jön a lényeg

abstract class AbstractFactory {
abstract public function getFirstProduct {}
abstract public function getSecondProduct {}
}

class ProductFactory extends AbstractFactory {
public function getFirstProduct() {
return new FirstProduct();
}

public function getSecondProduct() {
return new SecondProduct();
}
}
8

Ez az implementáció, nem a

BlaZe · 2013. Már. 2. (Szo), 14.12
Ez az implementáció, nem a különbség. Egész pontosan a soraidból nekem a lényeg nem jött át. Az abstract factory annyit definiál csak, hogy majd a belőle származtatott concrete factory-k legyártanak neked valamit (értelemszeren közös interface-szel). Ennek a legyártásnak a módját, illetve a legyártott konkrét interface implementációt meg majd a konkrét factory implementációk határozzák meg. Idáig írtad le. Egy példa, hogy érhető is legyen :)
interface DirLister {
  listDir(dir);
}

// lister implementacio - win
class WinDirLister implements DirListener {
  listDir(dir) {
    exec('dir ' + dir);
  }
}

// lister implementacio - unix
class UnixDirLister implements DirListener {
  listDir(dir) {
    exec('ls ' + dir);
  }
}

// abstract factory
abstract class AbstractDirListerFactory {
  createDirLister();
}

// concrete factory - win
class WinDirListerFactory extends AbstractDirListerFactory {
  createDirLister() {
    return new WinDirListener();
}

// concrete factory - unix
class UnixDirListerFactory extends AbstractDirListerFactory {
  createDirLister() {
    return new UnixDirListener();
}
És akkor ami igazából a lényege az egésznek, vagyis a pattern felhasználása:
class DirListerFunction {
  AbstractDirListerFactory dirListerFactory;

  setDirListerFactory(dirListerFactory) {
    this.dirListerFactory = dirListerFactory;
  }

  doListing(dir) {
    dirLister = dirListerFactory.createDirLister();
    dirLister.listDir(dir);
  }
}
Itt már van egy DI is, ez egy tipikus felhasználás. A lényeg, hogy van egy felhasználó osztály, ami kap egy AbstractFactory-t, amivel le tud magának gyártani egy valamit, amit fel tud használni a működéséhez. De nem kell tudja hogy konkrétan hogy kell legyártani azt a valamit, és hogy konkrétan mi az a valami. Így ez segíti a laza csatolást a különböző komponensek között.

Úgy általánosságban is szerintem érdemes elsősorban a problémát, célt felismerni a design patterneknél, amire megoldást adnak, nem az implementációt, különben nem tudod hol és miért kell bevetni.
9

Igen, valami ilyesmire

webproghu · 2013. Már. 2. (Szo), 14.58
Igen, valami ilyesmire akartam én is kilyukadni. Nyilván azért absztrakt, hogy a különböző gyártandó objektumokhoz meg lehessen határozni a konkrét factory-t, ami ténylegesen legyártja azt, míg a factory method esetében konkrétan egy "gyár" áll rendelkezésünkre, amely már implementálva is van. Abstract factory illetve factory method esetén nem árt egy interface-t is meghatározni a gyártandó objektumokhoz, hogy az "ügyfél", amelyik a gyárat használja, biztosan azt kapja, amit kért.

Ha már a Dependency Injection-t említetted, alapvetően ugye a célja az, hogy egy osztály függőségeit nem magában az osztályban példányosítjuk, hanem beinjektáljuk azt kontruktoron keresztül, metóduson keresztül (setter), vagy az objektum egy adattagján keresztül, így a rendszerünk komponensei sokkal egyszerűbben lecserélhetőek.
class User {
  protected $session;

  public function __construct () {
     $this->session = new Session();
  }
}

$user = new User();
Ahogy láthatjuk, épp a fentebb leírt eset forog fent, a user osztály konstruktorában "beégetve" példányosítjuk a session-t. DI-vel ez a következőképpen néz ki:
class User {
  protected $session;

  public function __construct (Session $session) {
     $this->session = $session;
  }
}

$session = new Session();
$user = new User($session);
A következő egyszerűbb minta ami eszembe jut, az a Registry: egyfajta központi tárolóként funkcionál az alkalmazásunkban gyakran tárolt objektumok eléréséhez.
class Registry {

private static $objects = array();

private function __construct() {
}

private function __clone() {
}

private function __wakeup() {
}

public static function get($key) {
	return self::$objects[$key];
}

public static function set($key, $value) {
	self::$objects[$key] = $value;
}

}
Ezzel is elérhetünk hasonló működést, mint a DI esetén:
$session = new Session();
Registry::set('session', $session);

class User {
  protected $session;

  public function __construct() {
    $this->session = Registry::get('session');
  }
}
10

Üdv! Esetleg megkérhetlek,

Karvaly84 · 2013. Már. 2. (Szo), 15.00
Üdv! Esetleg megkérhetlek, hogy a kódszínezőt vedd igénybe? Ott van jobb kéz fele a legalsó ikon. Figyelem a témát és sokkal átláthatóbb lenne ha használnád. Mások is örülnének neki sztem. :)
11

Rendben, elnézést, eddig nem

webproghu · 2013. Már. 2. (Szo), 15.01
Rendben, elnézést, eddig nem találtam meg. :)
12

Gyártófüggvény (factory

inf · 2013. Már. 2. (Szo), 16.01
Gyártófüggvény (factory method):
Felület meghatározása egy objektum létrehozásához, az alosztályokra bízva, melyik osztályt példányosítják. A gyártófüggvények megengedik az osztályoknak, hogy a példányosítást az alosztályokra ruházzák át.


Elvont gyár (abstract factory):
Kapcsolódó vagy egymástól függő objekumok családjának létrehozására szolgáló felületet biztosítani a konkrét osztályok megadása nélkül.


Építő (builder):
Az összetett objektumok felépítésének függetlenítése az ábrázolásuktól, így ugyanazzal az építési folyamattal különböző ábrázolásokat hozhatunk létre.

pl: PDFBuilder, RTFBuilder, ... ugyanazt az interface-t implementálja, de más a kimenetük.

Prototípus (prototype):
Ha ismered a javascriptet, akkor elég világos, hogy miről szól.
13

Igen, olvastam a könyvet, a

webproghu · 2013. Már. 2. (Szo), 17.43
Igen, olvastam a könyvet, a gond csak azzal van, hogy más könyvekben, leírásokban ellentmondanak a dolgok kicsit, illetve a magyar fordítás kicsit, khm, érdekes.

Az összetett objektumok felépítésének függetlenítése az ábrázolásuktól, így ugyanazzal az építési folyamattal különböző ábrázolásokat hozhatunk létre.


pl: PDFBuilder, RTFBuilder, ... ugyanazt az interface-t implementálja, de más a kimenetük.


Ööö, én ezt nem egészen így látom. A Builder egy-egy komplex objektumot épít, más-más módon. Egy egyszerű példa:
class Vehicle {
  private $type = '';
  private $color = '';
  private $numWheel = 0;

  public function setType($type) {
    $this->type = $type;
  }

  public function setColor($red) {
    $this->color = $color;
  }

  public function setNumWheel($numWheel) {
    $this->numWheel = $numWheel;
  }
}

class RedMotorCycleBuilder {
  public function build() {
    $vehicle = new Vehicle();
    $vehicle->setType('motorcycle');
    $vehicle->setColor('red');
    $vehicle->setNumWheel(2);
  }
}

$builder = new RedMotorCycleBuilder();
$redMotorCycle = $builder->build();
Ugyanezt a működést persze elérhetnénk öröklődéssel is, viszont véleményem szerint ez egy sokkal szebb út. Az oka az, hogy egy egyedet reprezentál, ha úgy tetszik egy főnevet, az viszont, hogy piros, 2 kereke van, és a típusa motorbicikli, tulajdonságok.

Prototípus:
ez tudtommal arra szolgál, hogy megspóroljuk az objektum példányosításába kerülő költséget, ezért létrehozunk egy prototípust, amelyet utána klónozunk. Ez főleg akkor hasznos, ha rengeteg azonos típusú objektummal dolgozunk:
class User {
  protected $id;
  protected $name;
  protected $email;

  public function construct($id) {
    $query = new MySQLQuery('SELECT id, name, email FROM users WHERE = '.(int)$id);
    $row = $query->fetchObject();
    $this->id = $row->id;
    $this->name = $row->name;
    $this->email = $row->email;
  }

  public function setID($id) {
    $this->id = $id;
  }

  public function setName($name) {
    $this->name = $name;
  }

  public function setEmail($email) {
    $this->email = $email;
  }
}
Látható, hogy ebben az esetben egy user példányosítása eléggé költséges, mivel minden egyes alkalommal lefuttatunk egy query-t. Ha 10000 felhasználót akarunk lekérni, akkor ez eléggé problémás lehet. Prototype-al:
abstract class AbstractUserPrototype {
  protected $id;
  protected $name;
  protected $email;

  abstract public function __clone();

  public function setID($id) {
    $this->id = $id;
  }

  public function setName($name) {
    $this->name = $name;
  }

  public function setEmail($email) {
    $this->email = $email;
  }
}

class UserPrototype extends AbstractUserPrototype {
  public function __clone() {
    // itt még csinálunk ezt-azt
  }
}

class UserCollection {
  public $users;

  public function __construct() {
    $this->users = array();

    $query = new MySQLQuery('SELECT id, name, title FROM users WHERE id BETWEEN 1 AND 10000'); 
    $userPrototype = new UserPrototype();
    while ($row = $query->fetchObject()) {
      $user = clone $userPrototype;
      $user->id = $row->id;
      $user->name = $row->name;
      $user->email = $row->email;

      $this->users[] = $user;
    }   
  }
}

$userCollection = new UserCollection();
14

Ööö, én ezt nem egészen így

BlaZe · 2013. Már. 2. (Szo), 18.35
Ööö, én ezt nem egészen így látom. A Builder egy-egy komplex objektumot épít, más-más módon. Egy egyszerű példa:

[...]
Ebben a példádban mi a különbség a builder és a factory között?


Ugyanezt a működést persze elérhetnénk öröklődéssel is, viszont véleményem szerint ez egy sokkal szebb út. Az oka az, hogy egy egyedet reprezentál, ha úgy tetszik egy főnevet, az viszont, hogy piros, 2 kereke van, és a típusa motorbicikli, tulajdonságok.

Szerintem nem, a motorbicikli a leszármazottja kell legyen a járműnek. A színe, rendszáma tulajdonság, de az, hogy motor, vagy autó, az a viselkedését befolyásolja. Gondolj egy turnLeft()-re...
16

Szerintem nem, a motorbicikli

webproghu · 2013. Már. 2. (Szo), 20.02
Szerintem nem, a motorbicikli a leszármazottja kell legyen a járműnek. A színe, rendszáma tulajdonság, de az, hogy motor, vagy autó, az a viselkedését befolyásolja. Gondolj egy turnLeft()-re...


Ebben igazad van, az, hogy motorbicikli, nem tulajdonság. A példa ilyen szempontból hibás, de ha vegyük ki a type-it a képletből, és így szerintem érthető lesz.

Ebben a példádban mi a különbség a builder és a factory között?


A Builder egy komplex objektum felépítésére, felparaméterezésére koncentrál, lényegében egy egyszerűbb felülelet kínál a felépítésére. A factory ezzel szemben egy feltétel alapján példányosít és visszaad egy (akár egyszerű akár komplex) objektumot, de nem törődik azzal, annak úgymond "felépítésével" mint a factory. Egy talán érthetőbb példa:
class Form {
  public function addTextInput($name) {
  }

  public function addPasswordInput($name) {
  }
} 
Ez egy form osztály, az egyszerűség kedvéért csak egy text, illetve egy password input hozzáadására szolgáló metódust raktam bele. Szeretnénk csinálni egy login formot. Ehhez valami ilyesmit csinálnánk:
$loginForm = new Form();
$loginForm->addTextInput('email');
$loginForm->addPasswordInput('password');
Ez eddig szép és jó, de szeretnénk a LoginForm-ot több helyen használni az alkalmazásunkban. Be copy-paste-elhetjük ezt a 3 sort, de jön a kérés, hogy adjunk hozzá egy checkboxot a form-hoz. Ezek után emlékezni fogsz, és visszakeresed, hogy hol használtak ilyen formot az alkalmazásban? Erre jó a builder minta. Csinálunk egy LoginFormBuilder-t:
class LoginFormBuilder {  
  public function build() {  
    $loginForm = new Form();
    $loginForm->addTextInput('email');
    $loginForm->addPasswordInput('password');
    return $form;
  }  
}  
Így ha bele kell rakni valamit a kódba, csak egy helyen kell megtenni, nem sérted meg a DRY elvet (dont repeat yourself), nem kell végigtúrnod a kódot, hogy hol lehet ilyen form és átírni. És gondolj bele, ha ez egy 30 mezőből álló form, és megkérik, hogy szedj ki belőle 5 mezőt, adj hozzá még 3-at. És a formot a kollégák 10 helyen használták az alkalmazásban. Szerintem ebből látszik, hogy mire jó és miben különbözik ez a minta a factory-től.
Te hogyan implementálnád ezt factory alkalmazásával?
17

Erre minek külön osztály?

inf · 2013. Már. 2. (Szo), 20.04
Erre minek külön osztály? Mármint ha jóval bonyolultabb, akkor esetleg lehet csinálni belőle egy LoginForm-ot, ami a Form-ból származik, vagy egy LoginFormFactory-t (ami ugyanez, csak nem builder neve), esetleg egy LoginFormContainer-t, ha dependency injection-re akarok utalni. Jelen állapotában nekem az aktuális View osztály egy createLoginForm nevű metódusa lenne. De hogy ennél a példánál maradjunk, ha van még egy View osztályom, ami ugyanazt az oldalt építi fel, csak mondjuk nem html5-ben, hanem xhtml-ben, akkor ugyanezek a metódusnevek fognak szerepelni benne, de az azokban található kód kicsit máshogy fog kinézni. Na ebben az esetben a két View neve lehet XHtmlBuilder és Html5Builder.
23

Mármint ha jóval

webproghu · 2013. Már. 2. (Szo), 20.31
Mármint ha jóval bonyolultabb, akkor esetleg lehet csinálni belőle egy LoginForm-ot, ami a Form-ból származik

A Form osztályból hogyan származna a LoginForm osztályod?
class Form {
  public function addTextInput() {
  }

  public function addPasswordInput() {
  }
}

class LoginForm extends Form {
  public function __construct() {
    $this->addTextInput();
    $this->addPasswordInput();
  }
}
?
Ez nem alami szép megoldás.

vagy egy LoginFormFactory-t (ami ugyanez, csak nem builder neve)


Egy olyan factory, ami egyfajta objektumot ad vissza? Na erre szolgál pont a Builder.

The Factory pattern can almost be seen as a simplified version of the Builder pattern.

In the Factory pattern, the factory is in charge of creating various subtypes of an object depending on the needs.

The user of a factory method doesn't need to know the exact subtype of that object. An example of a factory method createCar might return a Ford or a Honda typed object.

In the Builder pattern, different subtypes are also created by a builder method, but the composition of the objects might differ within the same subclass.

To continue the car example you might have a createCar builder method which creates a Honda-typed object with a 4 cylinder engine, or a Honda-typed object with 6 cylinders. The builder pattern allows for this finer granularity.


esetleg egy LoginFormContainer-t, ha dependency injection-re akarok utalni


Erre kérlek írj egy példát. És arra is, hogy szerinted miben különbözik a kettő. Tényleg kíváncsi vagyok a te meglátásodra.

De hogy ennél a példánál maradjunk, ha van még egy View osztályom, ami ugyanazt az oldalt építi fel, csak mondjuk nem html5-ben, hanem xhtml-ben, akkor ugyanezek a metódusnevek fognak szerepelni benne, de az azokban található kód kicsit máshogy fog kinézni. Na ebben az esetben a két View neve lehet XHtmlBuilder és Html5Builder.


A HTML-es példádra pont a Decorator pattern való. Van például egy TextInput osztályod, illetve egy HTML5Decorator és egy XHTML osztályod. Attól függően, hogy hogy akarod megjeleníteni, a HTML5Decoratorral, vagy az XHTMLDecoratorral dekorálod a TextInput-ot.
27

A Form osztályból hogyan

inf · 2013. Már. 3. (V), 06.13
A Form osztályból hogyan származna a LoginForm osztályod?

Pontosan úgy, ahogy írtad.

Ez nem alami szép megoldás.

Lehet, de ugyanúgy használható...

Egy olyan factory, ami egyfajta objektumot ad vissza? Na erre szolgál pont a Builder.

A könyv szerint, amit olvasok nem erre szolgál a builder, és a Gamma és társai féle Design patterns azért nekem többet nyom a latban, mint egy becopy-zott idézet.

Erre kérlek írj egy példát. És arra is, hogy szerinted miben különbözik a kettő. Tényleg kíváncsi vagyok a te meglátásodra.

Felépítésében nem különbözik a factory és a inversion of control container, annyi a különbség az ioc-nál, hogy a használati helyénél a container-t adod át, és nem a kész példányt. Én legalábbis nem tudok más eltérésről.

A HTML-es példádra pont a Decorator pattern való. Van például egy TextInput osztályod, illetve egy HTML5Decorator és egy XHTML osztályod. Attól függően, hogy hogy akarod megjeleníteni, a HTML5Decoratorral, vagy az XHTMLDecoratorral dekorálod a TextInput-ot.

Ezt kifejtenéd, én teljesen máshogy látom. A Decorator új tulajdonságok felvitelére jó, mondjuk ha van egy képed, akkor dekorálhatod egy kerettel, stb... A html5 meg xhtml meg két külön megjelenítési mód, ezeket nyilvánvalóan abstract factory-be kell kiszórni, ill. builder-rel kell megcsinálni magát az űrlapot. De erre írok is példát:


$view = new LoginView();
$view->display(new Html4Factory());


class LoginView
{
    protected $htmlBuilder;
    protected $loginFormFactory;

    public function __construct()
    {
        $this->htmlBuilder = new HtmlBuilder();
        $this->loginFormFactory = new LoginFormFactory($this->htmlBuilder);
    }

    public function display(ElementContainer $elementContainer)
    {
        $this->htmlBuilder->setElementContainer($elementContainer);
        header('content-type: ' . $elementContainer->getContentType() . '; charset=utf-8');
        echo $this->htmlBuilder->createHtml(array(
            $this->loginFormFactory->createLoginForm()
        ), 'my.page.net - Authorization')
    }
}


class LoginFormFactory
{
    protected $htmlBuilder;

    public function __construct(HtmlBuilder $htmlBuilder)
    {
        $this->htmlBuilder = $htmlBuilder;
    }

    public function createLoginForm()
    {
        return $this->htmlBuilder->createForm(array(
                $this->htmlBuilder->createTextInput('email'),
                $this->htmlBuilder->createPasswordInput('password')
            )
        );
    }
}

class HtmlBuilder
{
    protected $elementContainer;

    public function setElementContainer(ElementContainer $elementContainer)
    {
        $this->elementContainer = $elementContainer;
    }

    public function createHtml($content, $title = '')
    {
        return $this->elementContainer->createDocument(array(
                $this->elementContainer->createComplexElement('head',
                    $this->elementContainer->createComplexElement('title', $title)
                ),
                $this->elementContainer->createComplexElement('body', $content)
            )
        );
    }

    public function createForm($content, array $attributes = null)
    {
        return $this->elementContainer->createComplexElement('form', $content, $attributes);
    }

    public function createTextInput($name)
    {
        return $this->elementContainer->createSimpleElement('input', array('type' => 'text', 'name' => $name));
    }

    public function createPasswordInput($name)
    {
        return $this->elementContainer->createSimpleElement('input', array('type' => 'password', 'name' => $name));
    }

}


interface ElementContainer
{
    public function getContentType();

    public function createDocument($content);

    public function createSimpleElement($tagName, array $attributes = null);

    public function createComplexElement($tagName, $content = '', array $attributes = null);
}

abstract class AbstractElementFactory implements ElementContainer
{

    public function createComplexElement($tagName, $content = '', array $attributes = null)
    {
        return '<' . $tagName . $this->createAttributeList($attributes) . '>' . (is_array($content) ? implode('', $content) : $content) . '</' . $tagName . '>';
    }

    public function createAttributeList(array $attributes = null)
    {
        $html = '';
        foreach ($attributes as $attribute => $value)
            $html .= ' ' . $attribute . '="' . $value . '"';
        return $html;
    }
}

class XHtmlFactory extends AbstractElementFactory
{
    protected $contentType = 'application/xhtml+xml';
    protected $docType = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';

    public function getContentType()
    {
        return $this->contentType;
    }

    public function createDocument($content)
    {
        return $this->docType . $this->createComplexElement('html', $content, array('xmlns' => 'http://www.w3.org/1999/xhtml'));
    }

    public function createSimpleElement($tagName, array $attributes = null)
    {
        return '<' . $tagName . $this->createAttributeList($attributes) . '/>';
    }

}

class Html4Factory extends AbstractElementFactory
{
    protected $contentType = 'text/html';
    protected $docType = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> ';

    public function getContentType()
    {
        return $this->contentType;
    }

    public function createDocument($content)
    {
        return $this->docType . $this->createComplexElement('html', $content);
    }

    public function createSimpleElement($tagName, array $attributes = null)
    {
        return '<' . $tagName . $this->createAttributeList($attributes) . '>';
    }

}
Nem a legszebb, mondjuk a contentType-ot nem az ElementContainer-ben kellene tárolni, hanem kéne külön egy gyűjtemény, ami a content-type-hoz hozzárendeli a megfelelő factory-t, aztán az accept header-ben megadottak alapján szolgálja ki a böngészőt. Nyilván itt előjönne, hogy egy csomó féleképp lehet továbbítani egy ilyen űrlapot, akár még json-ban vagy xml-ben is, és a View-ból át lehetne rakni a kód egy részét mondjuk egy HtmlResponseFactory-be. Így a View is egy Builder lenne, aminél az accept header alapján be lehetne állítani, hogy milyen factory-t használjon a konkrét válasz előállítására. Nyilván ez ugyanúgy builder + abstract factory megvalósítása lenne csak egy másik rétegben. Ez a két minta szeret együtt járni... :-)

Azt hiszem a példa elég világosan mutatja, hogy mi a builder, és a factory között a különbség, ill. talán rávilágít arra is, hogy a IoC container (vagy DIC) inkább egy interface, ami nem szabja meg, hogy mi történjen a megvalósításon belül, tehát a container lehet factory, paraméterezett builder, singleton, vagy bármi egyéb, ami megfelel a container interface-nek.
26

Ez szerintem így még mindig

BlaZe · 2013. Már. 2. (Szo), 22.40
Ez szerintem így még mindig factory. A builder pattern értelme, hogy azt paraméterezd, majd a build metódus meghívására egy felparaméterezett példányt adjon vissza. Hatékony pl a fluent interface-szel kombinálva. Ilyesmi:

class Car {
  Color color;
  int doors;

  private Car() {}
    
  setColor(color) {
    this.color = color;
  }

  setDoors(doors) {
    this.doors = doors;
  }

  class Builder {
    Color color;
    int doors;

    Builder color(color) {
      this.color = color;
    }

    Builder doors(doors) {
      this.doors = doors;
    }

    Car build() {
      car = new Car();
      car.setColor(color);
      car.setDoors(doors);
      return car;
  }
}

car = new Car.Builder()
  .color(black)
  .doors(5)
  .build();
Érdemes esetleg megemlíteni, hogy ezek a patternek védenek a this escape ellen, vagyis amikor a konstruktorból kiadjuk a this referenciát más objektumoknak. Ez php-nál kevésbé érdekes, mert ha a referenciát akkor adjuk ki, amikor már minden propertyt rendesen beállítottunk, akkor nem lehet baj. De multithread programokban (javaban pl biztos) ez igen fincsi hibákat okozhat megfelelő csillagegyüttállás esetén.

Nem a fent vázolt a "hivatalos" megvalósítása a builder patternnek (legalábbis a wikipedia és a rá épülő tartalmak nem így mutatják be - itt nincs abstract, meg concrete builder), de szerintem ez jól megmutatja mire jó. Illetve azt is, hogy ezek a patternek mennyire a probléma megoldásáról szólnak, nem a megvalósításról, valamint hogy az egyes patternek között milyen átfedések vannak. Ezért sem érdemes ezeket bemagolni.
15

Ööö, én ezt nem egészen így

inf · 2013. Már. 2. (Szo), 20.00
Ööö, én ezt nem egészen így látom. A Builder egy-egy komplex objektumot épít, más-más módon.


Ez azért van, mert kevered a buildert a factory-vel. Amit te írsz azt két factory-vel lehet megcsinálni, és nem két builder-el. Ez is olyan kicsit, mint a hagyományos nyelvtanulás, nem jó keverni a szavakat... :-)
18

Lásd BlaZe hozzászólására

webproghu · 2013. Már. 2. (Szo), 20.04
Lásd BlaZe hozzászólására adott válaszomat egy görgetésnyivel fentebb. :) Vagy ha tévedek, akkor leírhatnád mire gondolsz, konkréten mit keverek. :)
19

factory = gyár, builder =

Karvaly84 · 2013. Már. 2. (Szo), 20.07
factory = gyár, builder = építész. ménemmennekazanjukbaezekkelanevekkel? sztem azok se tudják akik kitalálták melyik mire való :D
20

Szerintem elég világos, a

inf · 2013. Már. 2. (Szo), 20.10
Szerintem elég világos, a gyárból jön ki a kész termék, az építész meg megtervezi a gyárat, szóval eggyel magasabb absztrakciós szinten van.
21

Én bringákat építek, de

Karvaly84 · 2013. Már. 2. (Szo), 20.13
Én bringákat építek, de mindenki azt hiszi így jött ki a gyárból. Sztem nem egyértelmű és ezért nem értik sokan.
22

Akkor majd tudsz nekem jó

inf · 2013. Már. 2. (Szo), 20.15
Akkor majd tudsz nekem jó ülés márkát mondani :D Valamikor a tavasszal akarok új nyerget, a régi kényelmetlen túrához...
24

Kérdés h milyened van most?

Karvaly84 · 2013. Már. 2. (Szo), 20.37
Kérdés h milyened van most? Mi Velo nyereggel forgalmazzuk a gépeket abból is a legszakadtabbal, de ugyan ebből 6-7 ezeré már van zselés, ha meg minőség kell Selle tizenpárezeré vannak használhatóak.

Egyébként meg nem csak a nyereg lehet a probléma, hanem a rossz vázgeometria, helytelen ülésmagasság, stb
25

Fentebb bemásoltam egy angol

webproghu · 2013. Már. 2. (Szo), 21.02
Fentebb bemásoltam egy angol nyelvű példát a különbségre. A factory visszaadja egy objektum "altípusát". Például egy CarFactory-nak azt mondom, hogy kérek egy HondaCar-t, vagy egy FordCar-t. A Builder ezzel szemben felépít egy (azaz egyetlen egy) objektum típust, kérek egy HondaCar-t, pirosan, 4 hengerrel, 16 szeleppel. A fenti példáimban is ezt írtam, igazából lehet én vagyok a hülye, de nem látom miben keverném. :D

Tervezési minták könyvből másolva:

Építő (Builder) minta célja

Az összetett objektumok felépítésének függetlenítése az ábrázolásuktól, így ugyanazzal az építési folyamattal különböző ábrázolási folyamatokat hozhatunk létre.
28

Fentebb bemásoltam egy angol

inf · 2013. Már. 3. (V), 06.31
Fentebb bemásoltam egy angol nyelvű példát a különbségre. A factory visszaadja egy objektum "altípusát". Például egy CarFactory-nak azt mondom, hogy kérek egy HondaCar-t, vagy egy FordCar-t. A Builder ezzel szemben felépít egy (azaz egyetlen egy) objektum típust, kérek egy HondaCar-t, pirosan, 4 hengerrel, 16 szeleppel. A fenti példáimban is ezt írtam, igazából lehet én vagyok a hülye, de nem látom miben keverném. :D


Ott kevered, hogy a factory konrkét, a builder meg absztrakt/elvont.

kérek egy HondaCar-t, pirosan, 4 hengerrel, 16 szeleppel

itt nyilvánvalóan factory kell, mert konkrét példányt hozol létre:
CarFactory.createRedHondaWithFourCylinders()
a builder az lenne, ha elvont lenne, amit akarsz, pl kérsz egy piros 4 hengeres kocsit
CarBuilder.getRedCarWithFourCylinders()
A CarBuilder-ről meg nem tudod, hogy most éppen hondát vagy ferrarit csinál neked, attól függ, hogy a configban hogyan állítod be. Mondjuk lehet, hogy az alkalmazás nézi, hogy mennyi pénzed van, és ahhoz igazítja, hogy milyen típusú CarFactory-t használ. Ha sok pénzed van, akkor FerrariFactory-t állít be a CarBuilder-nél, ha meg kevés, akkor HondaFactory-t.
31

Ott kevered, hogy a factory

webproghu · 2013. Már. 3. (V), 11.50
Ott kevered, hogy a factory konrkét, a builder meg absztrakt/elvont.


Olvasd el a design patterns könyvet mégegyszer, keress rá neten, nem tudom miért hajtogatod, mindenhol le van írva nagyon egyszerűen, hogy mi is ez. A Builder is konkrét példányt hoz létre. Ezen felül ezt a példát már nem én írtam, hanem bemásoltam egy Builder-ról szóló cikkből...

Igazából én már nem tudom mit írjak neked, mert nem igazán vagy hajlandó tudomásul venni semmit. Tipikus példája az én mindent tudok esetnek. Rengeteg példát írtam, és másoltam be több helyről is leírást, hogy mi mire való. Te csak hajtogatod, hogy nem jó, azt, hogy miért nem, igazából példával nem támasztod alá. A neten rengeteg példa van rá, parancsolj:

https://github.com/kevbradwick/php-design-patterns/blob/master/Patterns/Builder/ProductBuilder.php

https://github.com/cbergau/PHPDesignPatterns/blob/master/builder.php

http://tinyweb.blogspot.hu/2012/07/builder-design-pattern-using-php.html

http://www.newthinktank.com/2012/09/builder-design-pattern-tutorial/

https://github.com/sinevar/php-design-patterns/blob/master/builder-design-pattern/implementation1/classes/WardrobeBuilder.php

http://architects.dzone.com/articles/does-builder-design-pattern

Különbség a factory és builder pattern között:

https://www.google.hu/search?q=factory+builder+k%C3%BCl%C3%B6nbs%C3%A9g&rlz=1C1CHMO_huHU503HU503&aq=f&oq=factory+builder+k%C3%BCl%C3%B6nbs%C3%A9g&aqs=chrome.0.59j62l3.8445&sourceid=chrome&ie=UTF-8https://www.google.hu/search?q=factory+builder+k%C3%BCl%C3%B6nbs%C3%A9g&rlz=1C1CHMO_huHU503HU503&aq=f&oq=factory+builder+k%C3%BCl%C3%B6nbs%C3%A9g&aqs=chrome.0.59j62l3.8445&sourceid=chrome&ie=UTF-8

Idézet:

Az Abstract Factory abban hasonlít a Builder-re, hogy az is összetett objektumokat építhet fel.
Az elsődleges különbség, hogy a Builder minta az összetett objektumok lépésről lépésre való
létrehozását helyezi középpontba. Az Abstract Factory esetében a termékobjektum-családok a
hangsúlyosabbak (akár egyszerű, akár összetett). A Builder minta utolsó lépésben visszaadja a
terméket, azonban az Abstract Factory minta, ami azt illeti azonnal.


http://stackoverflow.com/questions/757743/what-is-the-difference-between-builder-design-pattern-and-factory-design-pattern

Idézet:

Builder focuses on constructing a complex object step by step. Abstract Factory emphasizes a family of product objects (either simple or complex). Builder returns the product as a final step, but as far as the Abstract Factory is concerned, the product gets returned immediately.


https://groups.google.com/forum/?fromgroups=#!topic/comp.object/20s5FN3qUTE

Idézet:

Builder builds one complex object through several method calls. With
Abstract Factory, every method call returns its own little object.


The builder pattern encapsulates the logic of how to put together a
complex object so that the client just requests a configuration and the
builder directs the logic of building it. E.g The main contractor
(builder) in building a house knows, given a floor plan, knows how to
execute the sequence of operations (i,e. by delegating to subcontractors)
needed to build the complex object. If that logic was not encapsulated in
a builder, then the buyers would have to organize the subcontracting
themselves ("Dear, shouldn't we have asked for the foundation to be laid
before the roofers showed up?")


http://www.coderanch.com/t/149924/java-Architect-SCEA/certification/Difference-Abstract-Factory-Builder-Pattern

Idézet:

Abstract Factory pattern is about creating related families of objects.
Builder pattern is to decouple the representation from the complex construction proces, so that the same construction process can be used for different representations.


Google-ban rengeteget találsz még... De gondolom úgy is az lesz a válasz, hogy ők is rossz példákat írtak.

De nézzünk még egy számodra hitelesnek tartható forrásból is, Gamma féle Design Patterns könyv:

Builder

Cél: Az összetett objektumok felépítésének függetlenítése az ábrázolásuktól, így ugyanazzal az építési folyamattal különböző ábrázolásokat hozhatunk létre.

...

Kapcsolódó minták

Az Elvont gyár annyiban hasonlít az Építőhöz, hogy az is összetett objektumok megalkotására képes. A legfőbb eltérés közöttük, hogy az Építő minta az összetett objektumok lépésről lépésre történő létrehozását helyezi előtérbe, az Elvont gyár pedig termékobjektum-családokra helyezi a hangsúlyt (legyenek azok egyszerűek, agy összetettek). Az Építő utolsó lépésként adja issza az objektumot, az Elvont gyár viszont azonnal.
32

Elvont gyár annyiban hasonlít

BlaZe · 2013. Már. 3. (V), 13.17
Elvont gyár annyiban hasonlít az Építőhöz

Bahh... :D Miért kell mindent kényszeresen magyarítani a szakkönyvekben? Ki használt ilyen kifejezéseket valaha munka közben? :)

Szóval szerintem amúgy onnan kéne közelíteni a vitában, hogy mire való a 2 pattern, nem az implementáción vitatkozni. Aztán kiviláglana a különbség. Tökmind1, hogy absztrakt vagy nem, másra használjuk őket.
34

Szóval szerintem amúgy onnan

inf · 2013. Már. 3. (V), 13.35
Szóval szerintem amúgy onnan kéne közelíteni a vitában, hogy mire való a 2 pattern, nem az implementáción vitatkozni. Aztán kiviláglana a különbség. Tökmind1, hogy absztrakt vagy nem, másra használjuk őket.


Már ő is leírta az idézeteiben, hogy mire valók. A builder összetett objektumok felépítésére jó, és függetleníti a felépítésüket az ábrázolástól. Az ábrázolást abstract factory-vel lehet hozzácsapni, és attól függően változik, hogy milyen konkrét factory példányt használsz. Szóval a builder irányítja az abstract factory-t, tehát ő eggyel magasabb absztrakciós szinten van. Én legalábbis így használom, mert így látom értelmét, és a könyvben is kb ez van.
35

Az ábrázolást abstract

webproghu · 2013. Már. 3. (V), 14.06
Az ábrázolást abstract factory-vel lehet hozzácsapni, és attól függően változik, hogy milyen konkrét factory példányt használsz.


Az ábrázolást már megvalósítja az osztály, amiből a builder objektumot fog gyártani. Lehet kombinálni a kettőt, ahogy te a példádban tetted, de alapvetően mint két független pattern beszélünk róluk. A Builder-t inkább a Composite patternnel szokták együtt használni... Ez is le van írva a Tervezési minták könyvedben.
33

Nem igazán érdekelnek ezek a

inf · 2013. Már. 3. (V), 13.30
Nem igazán érdekelnek ezek a cikkek, nekem pont elég a design patterns, azért vettem meg. Egyébként írtam egy elég hosszú példát, hogy szerintem mi a különbség, csak átsiklottál felette.
36

Nem igazán érdekelnek ezek a

webproghu · 2013. Már. 3. (V), 14.08
Nem igazán érdekelnek ezek a cikkek, nekem pont elég a design patterns, azért vettem meg.


Ez ám a hozzáállás. Tehát ami nem téged igazol az téged nem érdekel. Egyébként a Design Patterns könyvből is kimásoltam, oda is tisztán és érthetően le van írva (példa is van ott), hogy mi a különbség, ami az összes többi linken is ott van.

Egyébként írtam egy elég hosszú példát, hogy szerintem mi a különbség, csak átsiklottál felette.


Nem siklottam át felette, ugyanazt csinálja a Builder-ed, mint amit eddig leírtam számtalanszor, csak te pluszban beinjektáltad egy factory-ba. Amit nem egészen értek, mert a factory ugye hasonló (de más) objektumok legyártására való, a builder pedig egyféle objektum másképpen való legyártására.
37

Jó, szerintem lapozzunk.

inf · 2013. Már. 3. (V), 15.01
Jó, szerintem lapozzunk.
29

Ez volt már? Ugyan angolul

deejayy · 2013. Már. 3. (V), 10.37
Ez volt már?

Ugyan angolul van, de elég sok DP-t felsorol és részletez.
30

Learning JavaScript Design

Poetro · 2013. Már. 3. (V), 11.11
38

webproghu

Hidvégi Gábor · 2013. Már. 3. (V), 15.02
Nem muszáj őket használni, ha ellentmondásosak, ráadásul aktív használói körében sincs teljes egyetértés. Biztosan megvan mindegyiknek a maga helye, de a kérdést neked kell feltenni, hogy szükséged van-e az adott mintára az adott környezetben? Főleg ilyen előzményekkel. A magam részéről úgy gondolom, az ilyen mesterséges szabályrendszerek bár sok esetben segíthetnek, de azért meg is köthetik az ember kezét, mert a gondolkodást ketrecbe zárják. Akinek kalapács van a kezében, mindent szögnek lát.
39

Nem muszáj őket használni, ha

BlaZe · 2013. Már. 3. (V), 15.22
Nem muszáj őket használni, ha ellentmondásosak, ráadásul aktív használói körében sincs teljes egyetértés.
Itt mire gondolsz?

A magam részéről úgy gondolom, az ilyen mesterséges szabályrendszerek bár sok esetben segíthetnek, de azért meg is köthetik az ember kezét, mert a gondolkodást ketrecbe zárják. Akinek kalapács van a kezében, mindent szögnek lát.
Amennyiben a jól karbantartható, újrafelhasználható kód stb ketrecbe zár :) Ezek nem l'art pour l'art dolgok, fontos eszközök a programtervezésben.
40

»Nem muszáj őket használni,

Hidvégi Gábor · 2013. Már. 3. (V), 15.36
»Nem muszáj őket használni, ha ellentmondásosak, ráadásul aktív használói körében sincs teljes egyetértés.«
Itt mire gondolsz?
Erre (a témafelvetésből):
nekem ezek nagyrészt homályosak, illetve sok esetben ellent is mondanak egymásnak

Ezek nem l'art pour l'art dolgok, fontos eszközök a programtervezésben.
Én ezt nem vonom kétségbe. Ha éjjel, egy kihalt úton szeretnél átkelni, megvárod mindig, amíg zöldre vált a lámpa?
44

Mindenre megpróbálod ráhúzni

BlaZe · 2013. Már. 3. (V), 16.33
Mindenre megpróbálod ráhúzni a csordaszellemet? :) Olvastál már a software design principles-ről?
41

Nem muszáj őket használni, ha

webproghu · 2013. Már. 3. (V), 15.51
Nem muszáj őket használni, ha ellentmondásosak


Nem maguk a minták ellentmondásosak, hanem a könyvek, példák, amik őket magyarázzák. Fentebb lehet is látni, hogy sok ember másként értelmezheti őket. Ezeknek az alkalmazása nyilván nem kötelező, jó ismerni őket, mert adott helyzetben hasznosak lehetnek. Én inkább ahhoz hasonlítanám őket, hogy ha házat építesz, akkor hogyan használd a kalapácsot és a szöget, ha a tetőd építed, ha a teraszt, vagy ha a kerítést. :)
42

Nem maguk a minták

Hidvégi Gábor · 2013. Már. 3. (V), 15.57
Nem maguk a minták ellentmondásosak, hanem a könyvek, példák, amik őket magyarázzák.
Ha valami egzakt, akkor azt hogy lehet különféleképp, ráadásul ellentmondásosan magyarázni?
43

Úgy, hogy egy minta nem

Joó Ádám · 2013. Már. 3. (V), 16.11
Úgy, hogy egy minta nem egzakt, csak egy minta :)
45

Azért ez egy kicsit sántít.

Hidvégi Gábor · 2013. Már. 3. (V), 16.41
Azért ez egy kicsit sántít. Hogy az ellentmondást szemléltessem: ha a kezedbe adnak egy kalapácsot, senki sem fogja úgy mutatni, hogy a nyelével üsd be a szöget. A fejével persze lehet három módon.
46

A magam részéről úgy

inf · 2013. Már. 3. (V), 19.26
A magam részéről úgy gondolom, az ilyen mesterséges szabályrendszerek bár sok esetben segíthetnek, de azért meg is köthetik az ember kezét, mert a gondolkodást ketrecbe zárják. Akinek kalapács van a kezében, mindent szögnek lát.


Valahol egyetértek veled, én sem úgy indulok neki egy kódnak, hogy ide hű de jó lenne ez meg ez a minta, hanem csinálom, ahogy jól esik, aztán vagy felismerem, vagy nem, hogy milyen mintát használok. Igazából már lassan a nevüket is elfelejtem. A solid elvek meg a refactoring sokkal többet ad a gyakorlatban, mint ezek a minták.
47

Így van

Hidvégi Gábor · 2013. Már. 3. (V), 19.32
Én még a KISS (Keep it simple, stupid) elvet tartom arany igazságnak.
48

Ja egyébként TDD remekül

inf · 2013. Már. 4. (H), 06.19
Ja egyébként TDD remekül beválik erre, amíg nem azzal fejlesztettem sokszor elkezdtek burjánzani teljesen feleslegesen a minták, most meg minden olyan egyszerű... I <3 it.