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