8.11 Composição

Fizemos, no ponto anterior, uma agregação. Agora nossa classe Conta tem um Cliente e associamos estas duas classes. Todavia, nossa classe Cliente existe independente da classe Conta . Suponha agora que nossa Conta possua um histórico, contendo a data de abertura da conta e suas transações. Podemos criar uma classe para representar o histórico, como no exemplo abaixo:

import datetime class Historico:
python
def __init__(self):
 
self.data_abertura = datetime.datetime.today() self.transacoes = []
 
python
def imprime(self):
python
print(f'data abertura: {} {self.data_abertura}') print("transações: ")
python
for t in self.transacoes: print("-", t)

Agora precisamos modificar nossa classe Conta de modo que ela tenha um Historico . Mas aqui, diferente da relação do cliente com uma conta, a existência de um histórico depende da existência de uma Conta :

class Conta:
python
def __init__(self, numero, cliente, saldo, limite=1000.0): self.numero = numero
 
self.cliente = cliente self.saldo = saldo self.limite = limite self.historico = Historico()
 

E podemos, em cada método para manipular uma Conta , acrescentar a operação nas transações de seu Historico :

class Conta:

código omitido

def deposita(self, valor): self.saldo += valor

self.historico.transacoes.append(f’depósito de {} {valor}’)

def saca(self, valor):
python
if (self.saldo < valor): return False
java
else:
 
self.saldo -= valor
 
self.historico.transacoes.append(f'saque de {} {valor}')
 
java
def extrato(self):
python
print(f'numero: {} \nsaldo: {} {self.numero, self.saldo}') self.historico.transacoes.append("tirou extrato - saldo

de {}“.format(self.saldo))

def transfere_para(self, destino, valor): retirou = self.saca(valor)
 

if (retirou == False): return False

else:
 
destino.deposita(valor) self.historico.transacoes.append(f'transferencia de {} para conta {} {valor, destino.numero}')
 
return True
 
E testamos:
 
cliente1 = Cliente('João', 'Oliveira', '11111111111-11') cliente2 = Cliente('José', 'Azevedo', '222222222-22') conta1 = Conta('123-4', cliente1, 1000.0)
 
conta2 = Conta('123-5', cliente2, 1000.0) conta1.deposita(100.0)
 
conta1.saca(50.0) conta1.transfere_para(conta2, 200.0) conta1.extrato
 
#numero: 123-4
 
#saldo: 850.0 conta1.historico.imprime()
 
#data abertura: 2018-05-10 19:44:07.406533 #transações:
 
#- depósito de 100.0 #- saque de 50.0
 
#- saque de 200.0
 

- transferencia de 200.0 para conta 123-5 - tirou extrato - saldo de 850.0

conta2.historico.imprime()

data abertura: 2018-05-10 19:44:07.406553 transações:

- depósito de 200.0

Quando a existência de uma classe depende de outra classe, como é a relação da classe Histórico com a classe Conta, dizemos que a classe Historico compõe a classe Conta . Esta associação chamamos Composição.

Mas, e se dentro da nossa Conta não colocássemos self.historico = Historico() , e ao invés disso tentássemos acessá-lo diretamente? Faz algum sentido fazer historico = Historico()?

Quando o objeto é inicializado, ele vai receber o valor default que definimos na classe:

class Conta:
python
def __init__(self, numero, cliente, saldo, limite): #iniciando outros parâmetros
 
self.historico = Historico()
 

Com esse código, toda nova Conta criada já terá um novo Historico associado, sem necessidade de instanciá-lo logo em seguida da instanciação de uma Conta .

Atenção: para quem não está acostumado com referências, pode ser bastante confuso pensar sempre em como os objetos estão na memória para poder tirar as conclusões de o que ocorrerá ao executar determinado código, por mais simples que ele seja. Com o tempo, você adquire a habilidade de rapidamente saber o efeito de atrelar as referências, sem ter de gastar muito tempo para isso. É importante nesse começo você estar sempre pensando no estado da memória. Além disso, lembrar que no Python “uma variável nunca carrega um objeto, e sim uma referência para ele” facilita muito.‌


⬅️ Capítulo Anterior | Próximo Capítulo ➡️