Introducció

La programació orientada a objectes va néixer amb Simula l'any 1962, i té com origen simular objectes i les interaccions entre objectes.

Un objecte és una estructura de dades (estat) amb una conjunt de funcions específiques de l’objecte que donen un resultat diferent en funció de l’estat de l’objecte (dels seus atributs).

Per últim, recorda que la programació orientada a objectes és molt útil en la creació de interfícies d’usuari, programes de simulació d’objectes reals (com disseny 3D), etc., però no en la gestió de processos que processen dades com pot ser el cas de la intel.ligència artificial, els serveis web, etc.

Objecte

Estructura de dades

Tot llenguatge de programació es composa de estructures de dades simples que et permeten representar qualsevol cosa, per exemple un rectangle:

rectangle_llargada = 3
rectangle_amplada = 6

Però si tenim dos rectangles, com podem representarlos?

rectangle_a_llargada = 3
rectange_a_amplada = 6

rectangle_b_llargada = 5
rectange_b_amplada = 10

No és una bona solució. Que tal si fem servir una llista?

rectangle_a = [3,6]
rectangle_b = [5,10]

D'aquesta manera tenim agrupades les propietats d'un rectangle en una única estructura de dades sota una sólo referència.

Funcions

I si volem calcular l'àrea d’un rectangle?

def area_rectangle(a,b):
    return a * b

def test():
    assert area_rectangle(3,4) == 12

Anem a provar si funciona.

Creem un nou projecte:

poetry add pytest --group dev
poetry shell

Instal·lem la dependència de pytest, en entorn de desenvolupament ja que no la necessitem en un entorn de producció (--group dev)

I activem l'entorn virtual:

poetry add pytest --group dev
poetry shell

Ja podem executar pytest:

$(app-py3-10) pytest

Però no havíem dit que feiem servir una llista per representar un rectangle?

def area_rectangle(rect):
   return rect[0] * rect[1]

def test():
   rectangle = [3,6]
   assert area_rectangle(rectangle) == 18

Tenim una funció que calcula l'àrea d’un rectangle … només si la llista que rep com a paràmetre representa un rectangle.

El que és cert és que en un codi una mica complexe poden passar moltes coses no previstes, perquè tu pots dir que una llista de dos valors és un rectangle, però per python és una llista i punt.

def area_rectangle(rect):
   return rect[0] * rect[1]

def test():

   rectangle = [3,6]
   assert area_rectangle(rectangle) == 18

   triangle = [3,6] # base ,altura
   assert area_rectangle(triangle) == 9
  

I et pot semblar evident perquè són poques línies, però a partir d’un centenar no és gens divertit.

Classes

Per això existeixen les classes en Python:

class Rectangle:
    def __init__(self, llargada, amplada):
        self.llargada = llargada
        self.amplada = amplada

def area_rectangle(rect):
    return rect.llargada * rect.amplada

def test():

    # un cub no és un rectangle
    rectangle = Rectangle(3,6)
    assert area_rectangle(rectangle) == 18

    triangle = [3,6] # base ,altura
    assert area_rectangle(triangle) == 9
$ pytest test.py
rect = [3, 6]
	def area_rectangle(rect):
>   	return rect.llargada * rect.amplada
E   	AttributeError: 'list' object has no attribute 'llargada'

Python utilitza el duck typing. Per tant la funció area_rectangle accepta qualsevol objecte que tingui dos atributs amb nom llargada i amplada.

class Rectangle:
   def __init__(self, llargada, amplada):
       self.llargada = llargada
       self.amplada = amplada

class Cub:
   def __init__(self, llargada, amplada, alçada):
       self.llargada = llargada
       self.amplada = amplada
       self.alçada = alçada

def area_rectangle(rect):
   return rect.llargada * rect.amplada

def test():

   # un cub no és un rectangle
   rectangle = Rectangle(3,6)
   assert area_rectangle(rectangle) == 18

   cub = Cub(3,6,2)
   assert area_rectangle(cub) == 36

Com que un cub té els atributs llargada i amplada la funció area_rectangle no té cap problema per calcular la seva area:

Quan veig una au que camina com un ànec, neda com un ànec i sona com un ànec, a aquesta au jo l’anomeno un ànec.

Els llenguatges que obliguen a declarar el tipus de paràmetre no tenen aquest problema,

Podem modificar la funció area_rectangle perquè verifiqui que el paràmetre és un instància de la classe Rectangle amb la funció isinstance:

class Rectangle:
   def __init__(self, llargada, amplada):
       self.llargada = llargada
       self.amplada = amplada

class Cub:
   def __init__(self, llargada, amplada, alçada):
       self.llargada = llargada
       self.amplada = amplada
       self.alçada = alçada

def area_rectangle(rect):
   assert isinstance(rect, Rectangle)
   return rect.llargada * rect.amplada

def test():

   rectangle = Rectangle(3,6)
   assert area_rectangle(rectangle) == 18

   cub = Cub(3,6,2)
   assert area_rectangle(cub) == 36

Ara enlloc de donar nos una dada errónea la funció genera un error en temps d’execució (en llenguatges com Java l’error es donaria en temps de compilació).

De totes maneres, si la funció area_rectangle només ha de funcionar amb objectes de la classe Rectangle perquè no juntar-ho tot?

class Rectangle:
   def __init__(self, llargada, amplada):
       self.llargada = llargada
       self.amplada = amplada

   def area(self):
       return self.llargada * self.amplada

def test():

   rectangle = Rectangle(3,6)

   assert rectangle.llargada == 3
   assert rectangle.area() == 18

Per cert, en UML la classe Rectangle es representa així:

classDiagram
  Rectangle: width
  Rectangle: height
  Rectangle: area()

Per facilitar la creació dels diagrames, pots usar la nostra guia per usar el plugin Mermaid en VSCode

Exercicis

A continuación tens una representació de clases en UML y has d'escriure el codi Pyhton corresponent.

Triángulo equilátero

classDiagram
  Triangle: base
  Triangle: height
  Triangle: area()
class Triangle:
   def __init__(self, base, height):
       self.base = base
       self.height = height

   def area(self):
       return self.base * self.height

Button

Anem a representar un botó d’una interfície gràfica:

classDiagram
  Button: bool active
  Button: String message
  Button: String click()

I la seva codificació en Python seria aquesta

class Button:
   def __init__(self, active, message):
       self.active = active
       self.message = message

   def click(self):
       if self.active:
           return self.message
       else:
           return ""

Window

Una de les característiques principals de la programació orientada a objectes és la composició d’objectes.

Per exemple una finestra d’una interfície gràfica pot tenir un butó:

classDiagram

class Window {  
  String title
  int width
  int heigth
  resize(width: double, heigth: double)
}

Window --> Button

class Button {
  bool active
  String message
  String click()
}

I la seva codificació en Python sería aquesta:

class Window:
   def __init__(self, title,width,heigth, content,button):
       self.title = title
       self.width = width
       self.height = heigth
       self.content = content
       self.button = button

   def resize(self,width,height):
       self.width = width
       self.height = height

Podem crear un instància de Window i verificar el seu funcionament:

def test():

   window = Window("DAW", 600,300,"Some content", Button(True,"hello"))
  
   message = window.button.click()
   assert message == "hello"

   window.button.active = False
   message = window.button.click()
   assert message == ""

   window.resize(200,400)
   assert window.width == 200
   assert window.height == 400

Herencia

TODO