fel
le

Az adatrejtés

Nagyon gyakori, hogy az osztály a mezőit private védelmi szinttel látja el, hogy védje a külvilág elől. Egy ilyen védelmi szintű mezőt a külvilág (osztályon kívüli kód) nem képes sem olvasni, sem írni direkt módon, csak az osztály publikus metódusain keresztül.

Képzeljük el azt az esetet, hogy az objektum-osztályunk egy mobiltelefont modellez le:

class TMobilTelefon
{
    public int PIN = -1;

    public bool PinEllenorzes( int pinKod)
    {
       if (pinKod==PIN) return true;
       else return false;
    }

    public bool PinBeallitas( int regiPinKod, int ujPinKod )
    {
       if (PIN==-1) PIN = ujPinKod;
       else
       {
            if (!PinEllenorzes(regiPinKod)) return false;
            PIN = ujPinKod;
       }
       return true;
    }
}
 

Nyilván nincs annak semmi értelme, hogy a telefon által tárolt PIN kódot publikussá tegyük, hiszen ekkor a külvilág képes annak értékét kiolvasni, így a telefon PIN kódja bármikor megváltoztatható:

class FoProgram
{
   public static void Main()
   {
      TMobilTelefon mob = new TMobiltelefon();
      ...
      mob.PinBeallitas( mob.PIN, 12345 );
   }
}
 

Célszerűnek tűnik a kritikus mező elrejtése a külvilág elől:

class TMobilTelefon
{
    private int PIN = -1;
    ...
}
 

Ekkor a fenti PIN mező nem kiolvasható, így nem alkalmazható a kód megváltoztatása során.

A túlzásba vitt adatrejtés

Nyilván hasonló problémákat okozhat, ha a mobiltelefonunk által használható egységek mennyiségét kell kezelnünk. Ha ezt publikussá tesszük, a külvilág direkt módon feltöltheti a telefont egységekkel telefonkártya-vásárlás nélkül is.

class TMobilTelefon
{
    private int EGYSEGEK_SZAMA;
    ...
}
 

De ha elrejtjük a külvilág elől ezt a mezőt, akkor annak értékét sem tudjuk kiolvasni:

class FoProgram
{
  public static void Main()
  {
    TMobilTelefon mob = new TMobiltelefon();
    ...
    // ez hibás sor, az ´EGYSEGEK_SZAMA´ mező nem elérhető!
    Console.WriteLine("A telefonon még {0} egység van.",mob.EGYSEGEK_SZAMA);
  }
}
 

Az adatrejtés szokásos megoldása

A szokásos megoldás, hogy a féltett, kritikus mezőket az osztályok elrejtik a külvilág elől egy korlátozó védelmi szinttel (private vagy protected), de biztosítunk public metódusokat a mező olvasására és írására.

A mező kiolvasását biztosító metódus a névadási hagyományoknak megfelelően get, az író metódust set formában szoktuk elnevezni:

class TMobilTelefon
{
    private int EgysegekSzama;

    public int getEgysegekSzama()
    {
       return EgysegekSzama;
    }

    public void setEgysegekSzama( int ujEgysegekSzama )
    {
       if (ujEgysegekSzama>0) EgysegekSzama = ujEgysegekSzama;
       else throw new Exception("Negatív szám nem megengedett!");
    }
}
 

A get függvény visszatérési típusa megegyezik a szóban forgó mező típusával, és nem fogad paramétert értelemszerűen. A metódus egyszerűen visszaadja a keresett mező értékét mint visszatérési érték. Amennyiben a mező értéke nem kiolvasható (mint amilyen a feljebb lévő PIN mező), úgy ezt a metódust egyszerűen nem írjuk meg.

A set metódus jellemzően egy paramétert fogad: a mező új értékét. A metódus megvizsgálja a paraméterben kapott új értéket, általában egy if-el, vagy egyéb módon. Amennyiben az értéket megfelelőnek találja - úgy eltárolja a mezőben. Ha hibás vagy értelmetlen értéket kapna - úgy megtagadja az érték tárolását. Ezt OOP világába egy kivétel feldobásával szoktuk jelezni.

A PROPERTY

A fenti megoldással az objektum biztosítja a külvilág felé az elrejtett mező irányában történő olvasás és írás lehetőségét - ellenőrzött módon. Az objektum a mezőbe történő érték eltárolás előtt lehetőséget kap annak ellenőrzésére, felülvizsgálatára.

A get és set függvények használatának egyetlen hátránya, hogy a felhasználói kód elég csúnya. Első ránézésre nem is látszik látványosan, hogy valójában egyszerűen egy mezőt írunk és olvasunk:

class FoProgram
{
   public static void Main()
   {
      TMobilTelefon mob = new TMobiltelefon();
      ...
      // telefonkártya feltöltése
      mob.setEgysegekSzama( mob.getEgysegekSzama() + 2000 );
   }
}
 

A property (magyarra fordítva jellemző, tulajdonság) ezen próbál segíteni. A property egy virtuális mező. Tréfásan ezt akár mezőnek látszó izé-nek is lehetne hívni:

class TMobilTelefon
{
    private int _EgysegekSzama;

    public int EgysegekSzama
    {
      get
      {
         return _EgysegekSzama;
      }
      set
      {
         if (value>0) _EgysegekSzama = value;
         else throw new Exception("Negatív szám nem megengedett!");
      }
    }
}
 

A fenti példában az EgysegekSzama nem metódus, mert akkor gömbölyű zárójelek állnának mögötte a formális paraméterlista leírása végett:

public int EgysegekSzama()
{
  ...
}
 

Az EgysegekSzama nem lehet mező sem, mert akkor a deklarációs sort egy pontosvessző zárná le, és nem lenne saját blokkja a { } jelek között.

public int EgysegekSzama;
 

A fenti példában szereplő EgysegekSzama egy property. A property-nek van típusa, valamint törzse, mely két részre osztható: get és set részre.

A property-t a külvilág azzal a szintakszissal használja, mintha az ténylegesen létező mező lenne:

class FoProgram
{
   public static void Main()
   {
      TMobilTelefon mob = new TMobiltelefon();
      ...
      // telefonkártya feltöltése
      int x = mob.EgysegekSzama; // olvasás
      mob.EgysegekSzama = x + 2000 ; // írás
   }
}
 

A get rész akkor hajtódik végre, amikor a property értékét a külső kód ki szeretné olvasni. A get-hez tartozó saját utasításblokk belsejében szereplő return-nek kell az értéket meghatározni. A return után írt kifejezés típusa természetesen meg kell egyezzen a property típusával, hiszen a külső kód ilyen típusú értéket vár. Ennek megfelelően a get rész egy beágyazott függvénynek is felfogható.

A set rész akkor hajtódik végre, amikor a property-be a külső kód értéket kíván írni. A set utasításblokkjában az elhelyezendő értéket a value tartalmazza. A value itt most egy konstansnak tekinthető, melynek típusa a property típusával egyezik meg.

A set utasításblokkjában a value-ben lévő értéket le szoktuk ellenőrízni, mielőtt ténylegesen befogadjuk. Ha az érték nem megfelelő, akkor a szokásos eljárás, hogy kivételt dobunk fel, ezzel jelezzük az elutasítást.

Csak írható property

A property törzse általában két részre bomlik: get és set. Ugyanakkor nem kötelező mindkét részt megírni. Amennyiben nem dolgozzuk ki a get, csak a set részt, úgy a property olvasását nem tesszük lehetővé a külvilág számára, csak az írást. Így egy csak írható property-t tudunk előállítani:

class TMalacPersely
{
    protected int _penz;

    public int penz
    {
       set
       {
         if (value>0) _penz += value;
         else throw new Exception("Nem csökkentheted "
               "a perselyben levő pénz mennyiségét!");
       }
    }
}
 

Csak olvasható property

A csak írható property mintájára elkészíthető a csak olvasható property is. Ez csak get szakaszt tartalmaz, így értéket nem helyezhetünk el benne, csak kiolvashatjuk annak értékét.

Az alábbi példa egy olyan property-t mutat be, amely mögött nincs tároló mező, hanem a kiolvasandó értéket on-line generáljuk:

class TKor
{
    public double sugar;

    public double Kerulet
    {
       get
       {
         return 2*sugar*Math.Pi;
       }
    }
}
 

Az adatrejtés problémaköre

Az alapprobléma az, hogy az osztály mezőinek értékére általában szabály van felírva. Ezen szabály azt azonosítja, hogy mely esetekben milyen értékeket vehet fel a mező ahhoz, hogy az objektum ne kerüljön ellentmondásba.

Például ilyen szabály az, ha egy Kor osztály sugar mezője nem vehet fel negatív vagy nulla értéket, mivel ilyen sugarú köröket a grafikus képernyőn nem tudunk megjeleníteni, értelmes módon.

Az objektumosztály megalkotásakor ezen szabályok folyamatos betartásáról saját magának kell gondoskodnia. Nem várható el az, hogy a főprogram ezen szabályokat egyébként ismerje, és nem hihetjük azt el, hogy a főprogram ezen szabályokat nem fogja megszegni.

Ha egy objektumosztály egy ilyen szabályt nem megfelelően tartat be, és e miatt valamely metódusa hibás működést produkál, akkor minden esetben az osztályt implementáló programozó a hibás!

Milyen esetek képzelhetőek el?

  • A mezőre semmilyen szabály nincs alkotva, értéke tetszőleges lehet. Ezen mező a külvilág számára szabadon, kontroll nélkül írható / olvasható, public.
  • A mező a külvilág felé csak olvasható. Ezt általában egy private vagy protected mezővel, és egy csak get részt tartalmazó property-vel szoktuk megoldani.
  • A mező a külvilág felé olvasható és írható, de a szabály betartása mellett. Ezt általában egy private vagy protected mezővel, és egy get és set részt is tartalmazó property-vel szoktuk megoldani, ahol a szabály leprogramozása a set részben történik meg.

Nyelvi különbségek

Nem minden OOP nyelv támogatja a property-k készítését. Amely nyelvven ez nem támogatott, úgy marad az eredeti megoldás: set és get metódusok készítése. Amely nyelven azonban ez támogatott, úgy érdemes ezt a megoldást használni, hiszen nem jelent többletmunkát az osztály készítőjének, de a felhasználói oldalon elegánsabb, jobban olvasható kód megírását teszi lehetővé.

Ugyanakkor le kell szögeznünk, hogy a felhasználói oldalt a property könnyen megtévesztheti. A felhasználói oldalon ugyanis nem könnyű megkülönböztetni a property-t a ténylegesen létező mezőktől, mivel mindkettőt ugyanazzal a szintaxissal kell használnia. De amíg a ténylegesen létező mező olvasása és írása direkt módon, nagy sebességgel történik, addig a property írása és olvasása minden esetben függvényhívással jár. Ez mindenképpen lassúbb, mint a mező esetén.

Hernyák Zoltán
2013-04-15 17:03:17