/ janturon.cz / Od kodéra k analytikovi / Vlastnosti (Properties)

Vlastnosti (Properties)

Motivace

Nastavení veřejného přístupu k atributům třídy je považováno za antivzor Object orgy, jímž si projekt zadělává na budoucí problémy. Mějme třídu reprezentující zboží obsahující informaci a procentuální slevě:

class Goods { public: int discountPercent; ... };

Časem se na mnoha místech systému mohou objevit následující metody:

void loyalCustomer(Goods g) { g.discountPercent+= 20; } void couponBonus(Goods g) { g.discountPercent+= 10; } void couponPriceOff(Goods g) { g.discountPercent+= 20; } void feast(Goods g) { g.discountPercent+= 30; } void wholesaler(Goods g) { g.discountPercent+= 10; } void quantityDiscount(Goods g) { g.discountPercent+= 20; } ...

V tuto chvíli je jasné, že výsledná seva může překročit i 100% a že její počítání se musí upravit. To by znamenalo na mnoha místech vložit opakující se kód s podmínkou (což by byl kódovací antivzor copy&paste), nebo vytvořit nastavovací metodu:

class Goods { int discountPercent; public: void setDiscountPercent(int percent) { discountPercent+= percent; if(discountPercent>50) discountPercent = 50; } int getDiscountPercent() { return discountPercent; } };

Pokud místo prostého přiřazení zavoláme přiřazovací metodu, kód se stane snadno udržitelným (maintainable): změnu je možné provést pouze na jednom místě.

Protože není předem známo, které atributy budou potřebovat nastavovací metodu, nastavíme ji všem veřejným. To ale znamená mnoho triviálních nastavovacích metod v kódu, což je antivzor Boilerplate.

Řešení odstraňující tyto dva extrémy (veřejné atributy a triviální nastavovací metody) je možnost pracovat s proměnnou stejným způsobem, ale místo přímého nastavení volat uživatelsky definovanou nastavovací metodu. Tuto funkčnost umožňují právě vlastnosti (properties). Nativní podporu má pro ně v jazyce C++ zatím jen kompilátor Visual Studia od Microsoftu. Je ale možné napsat vlastní implementaci, viz vpravo.

Řešení

Dobře napsané Vlastnosti se musí chovat při čtení a zápisu jako proměnné, musí mít však možnost při těchto operacících provést místo toho nastavovací metodu, a to bez zásahu do kódu.

To lze vyřešit pomocí šablon pro jednotlivé typy:

template <typename T> class property { // nastavovací metody T& getValue(); T& setValue(T value); public: // zápis T& operator=(const T& value) { setValue(value); return getValue(); } // čtení operator T() { return getValue(); } };

Přičemž nastavovací metody mohou provádět:

Dále by vlastnosti měly:

Hlavičkový soubor s takto implementovanými vlastnostmi je zde.

kontextuální přístupové metody

struct Test1 { int x; property<int,Test1> p; Test1() : p(this,&Test1::get,&Test1::set), x(0) { } void set(int& val) { x = val; } int& get() { return x; } };

globální přístupové metody

void setP(string& value, string& newValue) { value = newValue; } string& getP(string& value) { return value; } struct Test2 { property<string> p; Test2() : p(&setP,&getP) { } };

automaticky implementované přístupové metody

struct Test3 { property<short> p1; // plný přístup property<char> p2; // jen pro čtení Test3() : p1(0), p2('a',true,false) { } };

Syntaxe

Je možné kombinovat tvšechny možnosti čtecí a zápisové metody. Obecná syntaxe je:

property<type[,context]> P([context*][,getter|setter|bool][,setter|getter|bool]);

Kde context je kontext (třída), ve které je vlastnost použita (může se přeskočit, pokud žádná z přístupových metod není kontextuální). V konstruktoru za ním následuje kontextuální nebo globální metoda pro čtení nebo zápis, nebo true pro automaticky implementovanou čtecí metodu nebo false pro zákaz čtení. Pak může následovat kontextuální nebo globální metoda pro zápis nebo čtení (opačná než předchozí), nebo logický typ se stejným významem jako předchozí, ale pro zápisovou metodu. Výchozí pro obě metody je true. V případě výchozích metod může být jako první parametr místo kontextu konstruktor pro vytvoření proměnné, nad kterou jsou výchozí metody prováděny. Nepoužije-li se ani ten, dosadí se výchozí konstruktor typu vlastnosti. K proměnné vlastnosti je možné přistupovat přes P.value (pro čtení i zápis). Typ přístupových metod je následující:

kde u globálních metod je jeko první parametr dosazena value dané vlastnosti.