2016. szeptember 1., csütörtök

Polimorfizmus

      A polimorfizmus és a virtuális függvények segítségével olyan szoftverek fejleszthetők, melyek könnyű módszerrel bővíthetőek. Ezek a programok az osztály hierarchia minden objektumát az ősosztály objektumaiként kezelik, így az újonnan hozzáadott osztályok objektumait is rögtön kezelni tudják. Csak azoknál a programrészeknél kell módosítást végrehajtani, melyek specifikus információkat használnak az újonnan hozzáadott osztályról.

Switch
      A különböző adattípusok használatára a switch az egyik alternatív módszer, hiszen ezzel más-más utasításokat lehet végrehajtatni az objektum típusának függvényében. Például a formák osztályának hierarchiájában kiválasztható, hogy a melyik típusú formához melyik print() függvényt hívjuk meg. A switch-el azonban problémák is vannak. A programozó kifelejthet egy lehetséges esetet, például mikor egy új osztállyal bővíti a programot elfelejti beírni annak print() esetét a switch-be. Ugyanígy ha megváltoztatunk vagy kitörlünk egy osztályt, akkor mindenik switch-ből ki kell törölni annak esetét és, különben egy hibákkal teli program lesz az eredmény.

Virtuális függvények
      A switch logikájának automatizálását a virtuális függvények valósítják meg. Legyen egy Shape ősosztály, melynek deriváltjai a Point, Circle, Triangle, Rectangle és Square. Mindenik osztálynak van egy printShapeName függvénye, mely az adott forma nevét írja ki, ezért különbözőek. Jó lenne mégis, ha mindenik forma azonos módon kezelhető lenne, hisz egy ősosztályból származnak. Más szóval, ha az ősosztály szintjén meghívjuk a printShapeName függvényt, akkor a program dinamikus módon (futás közben) határozza meg, melyik derivált osztály függvényéről van szó. Ehhez a printShapeName függvényt virtuálisnak kell deklarálni az ősosztályban, és mindenik örökös felül kell írja a maga módján.

#include <iostream>
using namespace std;

class Shape{
     public:
           virtual void printShapeName() const{
                cout << "Shape::printShapeName()" << endl;
           }
           void init() const{
                cout << "Shape::init()" << endl;
           }
};

class Point : public Shape{
     public:
           void printShapeName() const{
                cout << "Point::printShapeName()" << endl;
           }
           void init() const{
                cout << "Point::init()" << endl;
           }
};

int main()
{
     Shape shape;
     cout << "A Shape függvényei Shape objektummal:" << endl;
     shape.printShapeName();
     shape.init();
    
     Shape* shapePtr = new Shape();
     cout << "\nA Shape függvényei Shape mutatóval:" << endl;
     shapePtr->printShapeName();
     shapePtr->init();
    
     Point* pointPtr = new Point();
     shapePtr = pointPtr;
     cout << "\nA Shape függvényei Point mutatóval, 1. eset:" << endl;
     shapePtr->printShapeName(); //polimorfizmus
     shapePtr->init();           //nem polimorfizmus
    
     Point point;
     shapePtr = &point;
     cout << "\nA Shape függvényei Point mutatóval, 2. eset:" << endl;
     shapePtr->printShapeName(); //polimorfizmus
     shapePtr->init();           //nem polimorfizmus
    
     return 0;
}

A kimenet:
A Shape fuggvenyei Shape objektummal:
Shape::printShapeName()
Shape::init()

A Shape fuggvenyei Shape mutatoval:
Shape::printShapeName()
Shape::init()

A Shape fuggvenyei Point mutatoval, 1. eset:
Point::printShapeName()
Shape::init()

A Shape fuggvenyei Point mutatoval, 2. eset:
Point::printShapeName()
Shape::init()

Az ősosztály printShapeName függvénye mutatóval vagy a pont operátorral is elérhető, akár egy sima függvény. Ugyanez érvényes a derivált osztály függvényeire is. Ha viszont az ősosztály típusú mutató egy derivált osztály objektumára mutat (shapePtr = pointPtr vagy shapePtr = &point), akkor a program automatikusan derivált osztályban lévő printSapeName függvényt veszi számításba (merthogy az ősosztályban virtuálisnak volt deklarálva). Ezt angolul „dynamic binding” azaz dinamikus csatolásnak vagy köteléknek nevezik.

Absztrakt ősosztályok
      Amikor az osztályra, mint adattípusra gondolunk, akkor arra számítunk, hogy az osztályt használó alkalmazásban lesznek majd ilyen típusú objektumok. Vannak viszont olyan körülmények, mikor az adott osztálynak nem deklarálunk objektumokat. Ezek az absztrakt osztályok és általában ősosztályok, ezért absztrakt ősosztályoknak hívjuk. Az ilyen osztályokból tehát nem deklarálunk objektumokat, ellenben ennek deriváltjaiból már igen, és ezért őket konkrét osztályoknak hívjuk. Az absztrakt osztályok túl alaposztályok, túl általánosak ahhoz, hogy bármit is kezdeni lehessen az objektumaival. Az ősosztály akkor lesz absztrakt, hogyha egy vagy több virtuális függvénye tiszta. Ez azt jelenti, hogy nincsen tartalma és ezt az =0 taggal jelzik a deklarálásnál: virtual void earnings() const = 0; . Ha ezt a függvényt a derivált osztály se írja felül, akkor ő is absztrakt osztálynak számít.

Polimorfizmus
      Lehetővé teszi, hogy a különböző osztályok objektumai, melyeket öröklés köt össze, másképp válaszoljanak az adott függvényre. Ezt a virtuális függvények teszik lehetővé, amint azt a fenti példában is láttuk. Természetesen a nem virtuális függvények is felülírhatók, erre ad példát az init() függvény. Minden esetben az ősosztály tagfüggvénye hívódott meg, mert bár a sharePtr a Point objektumra mutat, a típusa akkor is Shape. A típusátalakítás nem segíthet, hiszen nem a típus miatt nem működik, hanem a kiírás miatt, ugyanis minden esetben a Shape szemszögéből tesszük: shapePtr->init();. Ebben az esetben nincs polimorfizmus. A polimorfizmus elősegíti a program bővíthetőséget. Az ilyen stílusban írt alkalmazás független azoktól az objektumoktól akiknek üzenetet küld, azaz nem kell figyeljen arra, hogy a megfelelő tagfüggvény hívódjék meg és ezért nem is kell módosítani a program alapszerkezetét. Az új osztályok vagy objektumok hozzáadásakor nem kell újra végigmenni a teljes kódon, csak egyszerűen újra kell fordítani a fordító segítségével. Ha absztrakt ősosztályok is vannak a kódban, akkor nem deklarálhatunk nekik objektumokat, viszont mutatókat és referenciákat igen. Ezek felhasználhatók a derivált objektumok polimorfikus kezelésére. A polimorfizmus akkor is hasznosnak bizonyul, ha a fejlesztő nem tudja előre, hogy milyen osztályok kellenek majd a program végső verziójáig, mert új osztályokat a dynamic binding segítségével rögtön alkalmazni tudja majd. A virtuális függvényt használó objektum típusa nem fontos a fordítás során, majd a futásnál a virtuális függvény az őt használó objektum tagfüggvényeként lesz azonosítva.

Virtuális destruktorok
      Amikor polimorfizumst használunk a dinamikusan lefoglalt objektumokon, akkor problémák léphetnek fel azok lebontásánál. Amikor egy derivált objektumot, amelyet éppen az ősosztály használ, explicit módon lebontunk a delete operátorral, akkor az ősosztály destruktora hívódik meg attól függetlenül, hogy milyen típusú objektumról van szó. Hogy ez ne történjen meg, az ősosztály destruktorát is virtuálisnak deklaráljuk. Ettől aztán az összes derivált osztály destruktora virtuálissá válik még akkor is, ha más nevük van. A destruktorokkal ellentétben, a konstruktorokat nem lehet virtuálisnak deklarálni, de nincs is amiért.

Példa
      A következő programban a Shape ősosztálynak a Point, a Circle és a Cylinder lesznek a leszármazottjai. A Shape absztrakt ősosztály, mert a printShapeName és a print virtuális függvényei tiszták. Van még két virtuális függvénye, az area és a volume, amelyek nullát térítenek vissza, azaz önmagukban nem sokat érnek, de a Point osztálynak éppen megfelelnek, hiszen úgysem veszi hasznukat. A Circle a Point leszármazottja lesz és felülírja az area függvényt. A Cylinder a Circle leszármazottja és ő felülírja a volume függvényt.


shape.h
#ifndef SHAPE_H
#define SHAPE_H
class Shape
{
     public:
           virtual double area() const {return 0.0;}
           virtual double volume() const {return 0.0;}
           virtual void printShapeName() const = 0; // tiszta
           virtual void print() const = 0;          // tiszta
};
#endif

point.h
#ifndef POINT_H
#define POINT_H
#include <iostream>
using std::cout;
#include "shape.h"
class Point : public Shape
{
     public:
           Point(int = 0, int = 0);
           void setPoint(int, int);
           virtual void printShapeName() const {cout << "Point: ";}
           virtual void print() const;
     private:
           int x, y; // a pont koordinátái
};
#endif

point.cpp
#include <iostream>
#include "point.h"

Point::Point(int a, int b){
     setPoint(a, b);
}

void Point::setPoint(int a, int b){
     x = a;
     y = b;
}

void Point::print() const{
     cout << '[' << x << ", " << y << ']';
}

circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include "point.cpp"
class Circle : public Point
{
     public:
           Circle(double r = 0.0, int x = 0, int y = 0);
           void setRadius(double);
           virtual double area() const;
           virtual void printShapeName() const {cout << "Circle: ";}
           virtual void print() const;
     protected:
           double radius;
};
#endif

circle.cpp
#include <iostream>
using std::cout;
#include "circle.h"

Circle::Circle(double r, int a, int b) : Point(a, b){
     setRadius(r);
}

void Circle::setRadius(double r){
     radius = (r > 0 ? r : 0);
}

double Circle::area() const{
     return 3.14159 * radius * radius;
}

void Circle::print() const
{
     Point::print();
     cout << "; Sugár = " << radius;
}

cylinder.h
#ifndef CYLINDER_H
#define CYLINDER_H
#include "circle.h"
class Cylinder : public Circle
{
     public:
           Cylinder(double h = 0.0, double r = 0.0,
           int x = 0, int y = 0);
           void setHeight(double);
           virtual double area() const;
           virtual double volume() const;
           virtual void printShapeName() const {cout << "Cylinder: ";}
           virtual void print() const;
     private:
           double height;
};
#endif

cylinder.cpp
#include <iostream>
using std::cout;
#include "cylinder.h"

Cylinder::Cylinder(double h, double r, int x, int y) : Circle(r, x, y){
     setHeight(h);
}

void Cylinder::setHeight(double h){
     height = (h >= 0 ? h : 0);
}

double Cylinder::area() const{
     return 2 * Circle::area() + 2 * 3.14159 * radius * height;
}

double Cylinder::volume() const{
     return Circle::area() * height;
}

void Cylinder::print() const
{
     Circle::print();
     cout << "; Magasság = " << height;
}

test_shape.cpp
#include<iostream>
using std::cout;
using std::endl;
#include <iomanip>
using std::ios;
using std::setiosflags;
using std::setprecision;
#include "cylinder.h"

void virtualViaPointer(const Shape*);
void virtualViaReference(const Shape&);

int main()
{
     cout << setiosflags(ios::fixed | ios::showpoint) << setprecision(2); //double-nek
     Point point(7, 11);                 // x = 7, y = 11
     Circle circle(3.5, 22, 8);          // radius = 3.5, x = 22, y = 8
     Cylinder cylinder(10, 3.3, 10, 10); // height = 10, radius = 3.3, x = 10, y = 10

     point.printShapeName(); //static binding
     point.print();          //kiírja a koordinátákat
     cout << endl;

     circle.printShapeName(); //static binding
     circle.print();          //kiírja a koordinátákat + a sugarat
     cout << endl;

     cylinder.printShapeName(); //static binding
     cylinder.print();          //kiírja a koordinátákat + a sugarat + a magassgot
     cout << '\n';

     Shape *arrayOfShapes[3];     //3 elemű tömb, melyben Shape mutatók vannak
     arrayOfShapes[0] = &point;   //Az első a point objektumra mutat
     arrayOfShapes[1] = &circle;  //A második a circle objektumra mutat
     arrayOfShapes[2] = &cylinder;//A harmadik a cylinder objektumra mutat

     //Mindenik tagra meghívjuk a virtuális függvényeket kiíró függvényt
     cout << "\nA Shape virtuális függvényei mutatókkal meghívva\n";
     for(int i = 0; i < 3; i++) virtualViaPointer(arrayOfShapes[i]);
          
     //Mindenik tagra meghívjuk a virtuális függvényeket kiíró függvényt
     cout << "A Shape virtuális függvényei referenciákkal meghívva\n";
     for(int i = 0; i < 3; i++) virtualViaReference(*arrayOfShapes[i]);

     return 0;
}

//Meghívja a virtuális függvényeket a mutató segítségével
void virtualViaPointer(const Shape* baseClassPtr)
{
     baseClassPtr->printShapeName(); //dynamic binding
     baseClassPtr->print();          //dynamic binding
     cout << "\nTerület = " << baseClassPtr->area() << "\nTérfogat = " << baseClassPtr->volume() << "\n\n";
}

//Meghívja a virtuális függvényeket a referencia segítségével
void virtualViaReference(const Shape& baseClassRef)
{
     baseClassRef.printShapeName(); //dynamic binding
     baseClassRef.print();          //dynamic binding
     cout << "\nTerület = " << baseClassRef.area() << "\nTérfogat = " << baseClassRef.volume() << "\n\n";
}

A kimenet:
Circle: [22, 8]; Sugár = 3.50
Terület = 38.48
Térfogat = 0.00

Cylinder: [10, 10]; Sugár = 3.30; Magasság = 10.00
Terület = 275.77
Térfogat = 342.12

A Shape virtuális függvényei referenciákkal meghívva
Point: [7, 11]
Terület = 0.00
Térfogat = 0.00

Circle: [22, 8]; Sugár = 3.50
Terület = 38.48
Térfogat = 0.00

Cylinder: [10, 10]; Sugár = 3.30; Magasság = 10.00
Terület = 275.77
Térfogat = 342.12

A virtuális függvényeket meghívó virtualViaPointer és virtualViaReference függvények esetén a fordító nem tudja meghatározni, hogy milyen típusú lesz a baseClassPtr objektum, ami akár problémák a forrása is lehet.


Nincsenek megjegyzések:

Megjegyzés küldése