Hlavní navigace

Tvoříme "vláčky" nejen v C++

25. 1. 2012 1:02 (aktualizováno) | Ondřej Novák

Dnes bych se chtěl ve svém krátkém blogpostu podělit o programovací techniku, kterou nazývám „vláček“. Myslím, že to nebude nic neznámého, je to věc, která se dá uplatnit nejen C++, ale většinou u všech objektových jazyků, kde je volání prováděno zkrze zápis objekt.metoda(), jako třeba Java. V některých jazycích najdeme vláčky v základu (smalltalk – říká se tomu „message chain“).

Asi nejznámější vláček v C++ pozná každý, je to zápis do std::cout.

std::cout << "Součet " << a << " a " << b << " rovná se " << c << std::endl;

Vláček poznáme snadno, většinou je na začátku lokomotiva, díky níž je vláček vláčkem. Kdyby tam lokomotiva nebyla, překladač by nám vynadal. Pak následuje větší množství vagonků, které jsou pospojované stejným způsobem, a na konci někdy bývá speciální ukončující vagón, tedy něco, co vláček ukončuje a tím způsobí nějakou akci. Nemusí nutně vždycky, záleží na tom, zda chceme do vláčku zapojovat další vagonky. A nebo, pokud si objekt nepřeje vytvářet neuzavřené vláčky, spustí se akce na prvním středníku (zpravidla díky destruktoru u pomocného objektu vytvořeném lokomotivou, který tvoří spojení mezi vagony).

Vláčky lze psát i jinak, než s použitím přetížených operátorů. Můžeme použít normální funkce a zápis může být flexibilnější a čitelnější. Například v minulém blogpostu jsem psal o jedné třídě na spouštění procesů. Ukážu vám příklad jejího použití právě ve schopnosti tvořit vláčky.

Process proc("cmd.exe");
ProcessEnv env = Process::getEnv();

///.... zde celkem nevýznamný kód pracující s env.
integer retval = proc
    .setEnv(&env)
    .arg("/C")
    .arg(fname)
    .arg(localBase)
    .workDir(localBase)
    .stdOut(execOutput)
    .stdErr(execOutput)
    .exec();

Nenechte se zmást, že vláček je rozepsán na víc řádků. Opět je na začátku lokomotiva, zde proměnná proc, za lokomtivou jsou napojeny vagónky různých typů. Najdeme zde vagónek nesoucí environment, pak následují argumenty procesu, nastavení pracovní složky, přesměrování výstupu do souboru a tím pomyslným ukončujícím vozem je zde metoda exec(), která nakonec určuje, jak se s vláčkem bude zacházet. Když se podíváte na začátek, najdete tam, přiřazení do proměnné retval. I toto specifikuje metoda exec().

Ukážeme si ještě další vláčky, třeba vláček ve vláčku:

PFactory_t f = connection.createJSON();
PNode_t r = f->newArray();
r->add(f->newObject()
   ->add(f,"url",x.url)
   ->add(f,"title",x.title)
   ->add("lastVisit",f->newArray()
    ->add(f,x.lastVisit.wYear)
    ->add(f,x.lastVisit.wMonth)
    ->add(f,x.lastVisit.wDay)
    ->add(f,x.lastVisit.wHour)
    ->add(f,x.lastVisit.wMinute)
    ->add(f,x.lastVisit.wSecond)));

Výše uvedený příklad představuje pole objektů obsahující atributy url, title a lastVisit, přičemž poslední proměnná je pole. Výsledkem je objekt uložený v JSONu.

Vláčky se nám hodí tam, kde potřebujeme k objektům nastavovat mnoho atributů, případně atributy nelze jednoduše nastavit konstruktorem. Příkladem může být třeba funkce na otevření souboru: openFile(„foo.txt“).create()­.truncate().append().lockEx­clusive().chmod(0644);

Jak deklarovat vláčkový styl.

Deklarovat vláčkový styl je celkem snadné. Stačí, aby každá metoda, která má vystupovat jako vagónek, vracela referenci na objekt, který měnila (jednoduše na this). Pouze poslední „vagón“ buď obsahuje void, nebo vrací něco jiného.

class Prcoess {
public:
  Process &arg(const char *arg);
  Process &arg(natural number, natural base=10);
  Process &arg(integer number, natural base=10);
  Process &arg(float number);
  Process &arg(double number);
  Process &stdOut(const SeqFileOutput &stream);
  Process &stdErr(const SeqFileOutput &stream);
  Process &stdIn(const SeqFileInput &stream);
  Process &setEnv(const ProcessEnv *env);

  Process &workDir(ConstStrWpath);

  intexec();
  ...
  ...
};

Pakliže potřebujeme, aby se akce automaticky provedla na prvním středníku, musí lokomotiva vrátit namísto sebe sama pomocný objekt, který obsahuje deklarace vagonku. Vagonky si nadale přenášejí pouze referenci na pomocný objekt. Tento objekt má také destruktor, který ve svém těle vyvolá předem danou akci. 

Ještě jede příklad – bezpečný SQL dotaz

Vláčkový styl jsem použil i pro sestavování SQL dotazů:

MySQL::Query_tq(...);


q("REPLACE stats (`datum`,`counter`,`product_ver`,`partnerId`,`value`) "
  "VALUES (CURDATE() - INTERVAL %4 DAY,%1,%2,%3,%5)")
   .arg(iter->first.code)

   .arg(iter->first.versionId)

   .arg(iter->first.partnerId)

   .arg(iter->first.dateModif+age)

   .arg(iter->second.value)
   .exec();

Tady máme trochu komplikovanou lokomotivu, protože obsahuje vlastní SQL dotaz. Funkce arg jsou patřičně přetížené, takže řetězce například escapují tak, aby se zabránilo SQL-Injecting.

 PS: kdyby to někdo hledal, oficiálně se tomu říká Method Chaining