Na parte I demos uma passada rápida no conceito de aspectos. Agora veremos os mixins.
Mixins são classes incompletas que apenas atribuem determinado comportamento a suas herdeiras.
Vamos a um exemplo muito superficial, mas suficiente: um objeto que armazena notas de alunos em um arquivo.
from typing import TypeVar
from numbers import Integral, Number
from pickle import dumps, loads
import dbm
__all__ = ['Database', 'Grades']
FileType = TypeVar('File', str, '_gdbm.gdbm')
class Database:
def __init__(self, file: FileType=None):
if file is None:
file = 'grades.db'
if isinstance(file, str):
file = dbm.open(file, 'c')
self.file = file
def close(self) -> None:
if self.file:
self.file.close()
self.file = None
class Grades:
def __init__(self, registration: Integral, db: Database):
self.registration = registration
if not isinstance(db, Database):
raise TypeError('expected Database instance, got {.__name__}'
.format(type(db)))
self.db = db
try:
self.values = loads(db[bytes(str(registration), 'ascii')])
except KeyError:
self.values = {}
def save(self) -> None:
db[bytes(str(self.registration), 'ascci')] = dumps(self.values)
@property
def first_bimester(self) -> Number:
return self.values.get('1bim')
@first_bimester.setter
def first_bimester(self, value: Number) -> None:
self.values['1bim'] = float(value)
@property
def second_bimester(self) -> Number:
return self.values.get('2bim')
@second_bimester.setter
def second_bimester(self, value: Number) -> None:
self.values['2bim'] = float(value)
@property
def third_bimester(self) -> Number:
return self.values.get('3bim')
@third_bimester.setter
def third_bimester(self, value: Number) -> None:
self.values['3bim'] = float(value)
@property
def fourth_bimester(self) -> Number:
return self.values.get('4bim')
@fourth_bimester.setter
def fourth_bimester(self, value: Number) -> None:
self.values['4bim'] = float(value)
@property
def catch_up(self) -> Number:
return self.values.get('rec')
@catch_up.setter
def catch_up(self, value: Number) -> None:
return self.values['rec'] = float(value)
@property
def avg_grade(self) -> None:
grades = (
self.first_bimester or 0,
self.second_bimester or 0,
self.third_bimester or 0,
self.fourth_bimester or 0,
)
m = sum(grades) / len(grades)
ca = self.catch_up
return m if ca is None else (m + ca) / 2
Repare que temos o mesmo problema apresentando na parte I: está tudo misturado em uma única classe!
Podemos separar as partes de gerência de banco e serialização em classes diferentes, dedicadas a seu próprio aspecto, chamadas mixins.
A classe de faz serialização pode ser apenas isso:
class SerialisableGradeMixin:
def load(self) -> None:
s = self.retrieve()
self.values = loads(s) if s else {}
def __bytes__(self) -> None:
return dumps(self.values)
A gerência de banco vai para outro mixin:
class PersistenceMixin:
def retrieve(self) -> bytes:
try:
return self.db[bytes(str(self.registration), 'ascii')]
except KeyError:
return None
def save(self) -> None:
db[bytes(str(self.registration), 'ascii')] = bytes(self)
Preferindo, é possível separar a gerência de notas em um mixin também:
class GradesMixin:
@property
def first_bimester(self) -> Number:
return self.values.get('1bim')
@first_bimester.setter
def first_bimester(self, value: Number) -> None:
self.values['1bim'] = float(value)
...
@property
def catch_up(self) -> Number:
return self.values.get('rec')
@catch_up.setter
def catch_up(self, value: Number) -> None:
return self.values['rec'] = float(value)
@property
def avg_grade(self) -> None:
grades = (
self.first_bimester or 0,
self.second_bimester or 0,
self.third_bimester or 0,
self.fourth_bimester or 0,
)
m = sum(grades) / len(grades)
ca = self.catch_up
return m if ca is None else (m + ca) / 2
Ao final, a classe principal será apenas uma cola dos mixins:
class Grades(SerialisableGradeMixin, PersistenceMixin, GradesMixin):
def __init__(self, registration: Integral, db: Database):
self.registration = registration
if not isinstance(db, Database):
raise TypeError('expected Database instance, got {.__name__}'
.format(type(db)))
self.db = db
self.load()
A API da classe continua idêntica: recebe o número da matrícula e o banco na instanciação, propriedades para acessar as notas e método save
para
salvá-las em arquivo, porém agora cada aspecto está isolado e encapsulado
em seu próprio mixin.