Ugrás a tartalomhoz

Objektum orientált szoftverfejlesztés

Kondorosi Károly, Szirmay-Kalos László, László Zoltán

ComputerBooks Kft.

7.5. Nem objektumorientált környezethez, illetve nyelvekhez történő illesztés

7.5. Nem objektumorientált környezethez, illetve nyelvekhez történő illesztés

Egy szépen megírt objektum-orientált program abból áll, hogy objektumai egymásnak üzeneteket küldözgetnek. "Címzett nélküli" metódusok (globális függvények), amelyek más környezetek, programozási nyelvek (pl. C, assembly nyelvek, stb.) alapvető konstrukciói csak kivételes esetekben fordulhatnak elő bennük. Gyakran azonban szükségünk van a két, eltérő filozófiára alapuló programok összeépítésére.

Minden C++ program, mintegy a C-től kapott örökségképpen, egy globális main függvénnyel indul. Eseményvezérelt programozási környezetek (például az ablakozott felhasználói felületeket megvalósító MS-Windows vagy X-Window/MOTIF), ezenkívül a külső eseményekre az alkalmazástól függő reakciókat globális függvények hívásával aktivizálják.

Mindenképpen meg kell küzdenünk az illesztés problémájával, ha más programozási nyelveken megírt függvényeket kívánunk változtatás nélkül felhasználni, illetve olyan processzorközeli szolgáltatásokra van szükségünk, ami a C++ nyelven nem, vagy csak igen körülményesen megvalósítható, és ezért részben assembly nyelven kell dolgoznunk.

A különböző rendszerek között a kapcsolatot természetesen úgy kell kialakítani, hogy az objektum-orientált megközelítés előnyei megmaradjanak, tehát az objektumaink továbbra is csak a jól-definiált interfészükön keresztül legyenek elérhetők. Ezt a koncepciót kiterjesztve a nem objektum-orientált részre, annak olyan publikus függvényeket kell biztosítania, amelyeket az objektum-orientált rész aktiválhat.

A különbség az objektum-orientált és nem objektum-orientált nyelvek között alapvetően az, hogy az előbbiekben egyaránt lehetőségünk van arra, hogy globális függvényeket hívjunk meg és a (látható) objektumoknak üzenetet küldjünk, míg az utóbbiban csak függvényeket aktivizálhatunk. Mivel az objektum-orientált programozási nyelvek tartalmazzák a nem objektum-orientált nyelvek konstrukcióit is, a nem objektum-orientált rész szolgáltatásainak, azaz függvényeinek a hívása nem jelent semmilyen gondot. Az áttekinthetőség kedvéért létrehozhatunk külső, nem objektum-orientált szolgáltatásokat lefedő interfész-objektumokat, melyek metódusai az elítélt globális függvényhívásokat egy objektumra koncentrálják.

Amennyiben a nem objektum-orientált részből hívjuk az objektum-orientált részben definiált objektumok metódusait, azt csak globális függvényhívással tehetjük meg. Így gondoskodnunk kell az üzenetre történő átvezetésről, amely során a célobjektum címét is meg kell határozunk. Ha csupán egyetlen objektum jöhet szóba, akkor az objektumot, vagy annak címét globális változóként definiálva a fenti üzenetváltás elvégezhető. Az alábbi példában egy A típusú a objektumnak f üzenetet küldünk az F globális függvény meghívásával:

      
      class A {
         void f( ) { ... }
      };
      
      A a;
      
      void F( ) {
         a.f( );
      }
      
      

Ennek a megoldásnak a speciális esete a program belépési pontját képviselő main függvény, amelyben az applikációs objektumot indítjuk el. A main függvényből a program futása alatt nem léphetünk ki, így az applikációs objektum csak a main függvényben él, tehát nem kell feltétlenül globális objektumnak definiálni:

      
      void main( ) {       // NEM OOP -> OOP interfész
         App app;
         app.Start( );
      }
      
      

Amennyiben több objektumnak is szólhat a nem objektum-orientált környezetből érkező üzenet, a címzettet a függvény argumentumaként kell megadni. Ez általában úgy történik, hogy a mutatókat az inicializálás során az objektum-orientált rész közli a nem objektum-orientált résszel. Természetesen a nem objektum-orientált részben a mutatótípusokon módosítani kell, hiszen a nem objektum-orientált környezet az osztályokat nem ismeri, ezért ott célszerűen void * típust kell használni:

      
      class A {
         void f( ) { ... }
      };
      
      void F( void * pobj ) {
         ((A *) pobj ) -> f( );
      }
      
      

Az eseményvezérelt környezetekben a meghívandó függvény címét adjuk át a nem objektum-orientált résznek. A nem objektum-orientált rész a függvényt egy előre definiált argumentumlistával, indirekt hívással aktivizálja. Itt élhetünk a korábbi megoldásokkal, amikoris egy globális közvetítőfüggvény címét használjuk fel, amely egy globális objektum vagy címváltozó alapján adja tovább az üzenetet a célobjektumnak. Felmerülhet bennünk a következő kérdés: miért van szükség erre a közvetítő függvényre, és miért nem egy tagfüggvény címét vesszük? Közvetlenül egy tagfüggvényt használva megtakaríthatnánk egy járulékos függvényhívást és egyúttal az objektum-orientált programunkat "elcsúfító" globális függvénytől is megszabadulhatnánk. A baj azonban az, hogy a függvénycím csak egy cím függetlenül attól, hogy mögötte egy osztály metódusa, vagy csupán egy globális függvény áll. A C++ lehetővé teszi, hogy tagfüggvények címét képezzük. Egy A osztály f metódusának a címét az &A::f kifejezéssel állíthatjuk elő. A bökkenő viszont az, hogy az üzenetkoncepció értelmében ezeket a metódusokat csak úgy lehet meghívni, hogy első argumentumként a célobjektum címét (this mutató) adjuk át. A C++ fordítók igen kényesek arra, hogy nehogy elmaradjon a tagfüggvény hívásokban a láthatatlan this mutató, ezért a tagfüggvények címével csak igen korlátozottan engednek bánni, és megakadályozzák, hogy azt egy globális függvény címét tartalmazó mutatóhoz rendeljük hozzá.

A szigorúság alól azért van egy kivétel, amely lehetővé teszi a probléma korrekt megoldását. Nevezetesen, ha egy tagfüggvényt statikusként (static) deklarálunk, akkor a hívása során a megcímzett objektum címe (this mutató) nem lesz átadott paraméter. Ebből persze következik, hogy az ilyen statikus metódusokban csak a statikus adatmezőket érhetjük el, a nem statikusakat, tehát azokat, melyekhez a this mutató is szükséges, nyilván nem.