POO Herència

De Cacauet Wiki
Dreceres ràpides: navegació, cerca

Herència és el mecanisme pel qual podem crear objectes (fills) que adopten les característiques d'un altre (pare) i que les extenen o sobreescriuen amb nous mètodes o atributs.

Podeu fer una ullada a aquest article arxiconegut sobre animals i herència.

Herència bàsica[modifica]

En Python la herència s'introdueix entre parèntesi. En aquest exemple Pilota() no hereda de ningú i PilotaDeTennis hereda les propietats (atributs i mètodes) de Pilota.

class Pilota():
   # atributs
   velocitat = 10
   posx = 10
   posy = 12
   # mètodes
   def accelera(self):
      self.velocitat = self.velocitat + 5
 
class PilotaDeTennis(Pilota):
   color = "verd"
   def accelera(self):
      self.velocitat = self.velocitat + 12

Tinguem en compte que:

  • PilotaDeTenis hereda els atributs posx, posy i velocitat de Pilota.
  • Sobreescrivim el mètode accelera(), i enlloc d'accelerar 5 unitats (el mètode definit en Pilota), la PilotaDeTennis accelerarà 20 unitats.
>>> pelo = Pilota()
>>> ptennis = PilotaDeTennis()
>>> pelo.velocitat
10
>>> pelo.accelera()
>>> pelo.velocitat
15                          # Pilota ha accelerat 5 punts
>>> ptennis.velocitat
10
>>> ptennis.accelera()
>>> ptennis.velocitat
22                          # PilotaDeTennis ha accelerat 12 punts


"Noves classes" de Python[modifica]

Llegir: http://docs.python.org/reference/datamodel.html#new-style-and-classic-classes

A partir de la versió 2.1 tots els objectes els heredem de "object". Es solventen alguns problemes antics. Es podria haver posat per defecte, però s'ha mantingut per la compatibilitat amb les aplicacions anteriors.

L' "estil antic" de classes Python (prèvies a 2.1) es defineixen:

class Pilota():
   ...

Ara, per definir un objecte "a la manera nova": OJU! sempre ho farem així en principi

class Pilota(object):
   ...

La diferència apreciable més important son els mètodes que heredem de "object":

>>> dir(Pilota)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__',
 '__getattribute__', '__hash__', '__init__', '__module__', '__new__',
 '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
 '__str__', '__subclasshook__', '__weakref__', 'accelera', 'posicio',
 'posx', 'posy', 'velocitat']


Constructors i herència: instrucció super()[modifica]

Quan creem una classe derivada i sobreescrivim el constructor (mètode __init__ en Python), el constructor del pare no es crida. Per exemple:

class A(object):
   def __init__(self):
      print "construint l'objecte..."
 
class B(A):
   def __init__(self):
      print "hola què tal?"

Ara comprovem què passa:

>>> a = A()
construint l'objecte...
>>> 
>>> b = B()
hola què tal?

Com podem veure, el constructor de A() no es crida al crear un objecte B(). Com que molt sovint es necessita cridar-lo, es pot fer amb la instrucció super(). Oju que aquest exemple és per Python 2.x. En Python 3.0 s'ha simplificat a super(), sense arguments, molt més elegant:

# exemple en Python 2.x
class A(object):
   def __init__(self):
      print "construint l'objecte..."
 
class B(A):
   def __init__(self):
      super(B,self).__init__()
      print "hola què tal?"

ULL!: si no creem l'objecte base A(object) heredat de "object", l'exemple no funcionarà.

Comprovem-ho:

>>> a = A()
construint l'objecte...
>>> b = B()
construint l'objecte...
hola què tal?


Herència múltiple[modifica]

...


Polimorfisme[modifica]

El polimorfisme és una important propietat de la POO, molt rellevant sobretot en llenguatges on no hi ha tipus dinàmics com el C++ o el Java. El què significa és que una referència (punter) a un objecte genèric (pare), al cridar a una funció que està sobrescrita per un objecte derivat, executarà la funció més "nova", és a dir, la de l'objecte instanciat realment.

El cas típic és tenir un array de punters a objectes pare que no sabem de quin tipus derivat exactament son, per exemple, un array de Pilotes. Segons com l'haguem creat, el punters apuntaran a objectes dels tiups PilotaDeTennis, PilotaDeFutbol i PilotaDeBasket, però no tenim perquè saber-ho, tot i que sabem segur que tots hereden de Pilota. Quan cridem al mètode accelera() s'executarà el de l'objecte derivat, no el del pare genèric, encara que el punter apunti a Pilota.

En el cas del Python no ens resulta molt rellevant ja que tenim tipus dinàmics amb total introspecció. Això vol dir que en tot moment sabem de quin tipus és cada objecte. A sobre, en Python podem tenir arrays d'objectes heterogenis, cosa que no es pot fer amb Java o C++. Així, el polimorfisme no en aquests llenguates és molt important, però no en Python.

Per saber més podeu llegir:


Mètodes i classes virtuals[modifica]

En Python tampoc és molt rellevant aquest aparat ja que està relacionat amb el Polimorfisme.

Un mètode virtual és aquell que, en ser cridat, intentarà cridar al de la classe derivada en cas que existeixi. Si no definim com a virtual un mètode es cridarà al de la classe del punter referenciat. Podeu veure l'exemple en C++ a POO: Polimorfisme.

Un mètode virtual pur és aquell mètode virtual que no s'ha definit. En C++:

class Pilota {
   ...
   virtual int accelera()=0;
   ...
   }

Una classe virtual és aquella que al menys té un mètode virtual pur. Una classe virtual no es pot instanciar. Cal crear una classe derivada que implementi el/s mètode/s virtual/s per poder ser instanciada.