Django – Herança, Modelos Abstratos

Django – Herança, Modelos Abstratos e OO Na Veia

Uma coisa que percebi a algum tempo, é que existem poucas referencias à isso nos tutoriais de modo geral, e que usar os conceitos de OO em Django ajudam, e muito, quando seus módulos começam a ficar meio “repetido”.
Felizmente depois de começar a trabalhar na SparkIt, meu colega Victor Fontes me deu umas dicas de como fazer isso sem problemas. E gostaria de compartilhar isso com vocês.

Herança

Segue a um cenario de exemplo:
No exemplo a seguir vemos duas classes, A e B que tem um campo em comum: Nome.
1234567
class A(C):
nome = models.CharField(u"Nome", max_length=250)
atributo_a = models.CharField(u"Atributo de A", max_length=250)
 
class B(C):
nome = models.CharField(u"Nome", max_length=250)
atributo_b = models.CharField(u"Atributo de B", max_length=250)

Problemas:
  • Rescrever Codigo:
      Como pode se perceber você acaba reescrevendo codigo que nem um louco(imagine se fossem 10 classes e todas tivessem um atributo Nome!)
  • Manutenção:
      Imagina se você decide mudar o campo nome pra que ele tenha max_length=100, teria que sair alterando todas as classes.
Então vemos de cara que podemos usar o conceito mais basico de OO: Herança.
Vamos criar uma classe pai C que ira Generalizar A e B:
12345678
class C(models.Model):
nome = models.CharField(u"Nome", max_length=250)
class A(C):
atributo_a = models.CharField(u"Atributo de A", max_length=250)
 
class B(C):
atributo_b = models.CharField(u"Atributo de B", max_length=250)

Vantagens:
  • Reutilização de Codigo:
      Você escreve uma classe que possui o(s) campo(s) genéricos e depois herda dessas classes. Não tem que sair escrevendo campos/métodos em varias classes.
  • Manutenção:
      Mudar o campo nome pra que ele tenha max_length=100 seria necessário apenas mudar o atributo na classe C.
Problemas:
Os models são classes que são persistidas em bancos de dados, correto?
Aí, entra o problema.
No banco de dados as tabelas ficaram(mais ou menos) da seguinte forma:
A(id_c,atributo_a)
B(id_c,atributo_b)
C(id, nome)
Isso é, se fizermos uma consulta:
A.objects.get(nome=”Arruda”)
O django ira fazer um Join de A + C para poder pegar o atributo Nome de A.
E isso irá fazer com que suas consultas mais simples se tornem muito pesadas.(Imagine um join numa tabela C que tem os nomes de todos os dados de 20 classes diferentes… é bastante coisa né).
Logo ficamos desencorajados a usar Herança como deve ser usada.
Mas, existe uma solução:

Modelos Abstratos

No Django é possivel definir um model como sendo abstrato, isso é: Não é persistido no banco de dados.
O exemplo anterior ficaria da seguinte forma(fazendo a classe C como Abstrata):
1234567891011
class C(models.Model):
nome = models.CharField(u"Nome", max_length=250)
 
class Meta:
abstract = True
class A(C):
atributo_a = models.CharField(u"Atributo de A", max_length=250)
 
class B(C):
atributo_b = models.CharField(u"Atributo de B", max_length=250)

Vantagens:
  • Sem Joins:
      Diferente de quando fizemos com uma classe normal, ao usar uma abstrata todos os campos de C são criados nas tabelas que herdam do mesmo.
  • Além de todas as vantagens de usar Herança.
Isso é algo que permite uma grande variadade de coisas, ainda mais quando usamos Herança Múltipla.

Herança Múltipla

Vamos incrementar o exemplo, vamos supor que tanto A quanto B tenham chave estrangeira para uma classe D.
Com isso podemos criar uma classe F que faca essa ligação:
1234567891011121314151617
class F(models.Model):
chave_f = models.ForeignKey('nomeApp.D')
 
class Meta:
abstract = True
 
class C(models.Model):
name = models.CharField(u"Nome", max_length=250)
 
class Meta:
abstract = True
 
class A(C, F):
atributo_a = models.CharField(u"Atributo de A", max_length=250)
 
class B(C, F):
atributo_b = models.CharField(u"Atributo de B", max_length=250)

Com isso, nossas classes A e B ambas tem os campos Nome e uma chave estrangeira para D.
Mas pera ai! E se eu quiser definir um related_name?
Nesse caso fazemos um pequeno ajuste na classe F:
1234567891011121314151617
class F(models.Model):
chave_f = models.ForeignKey('nomeApp.D',related_name='meus_%(class)ss')
 
class Meta:
abstract = True
 
class C(models.Model):
name = models.CharField(u"Nome", max_length=250)
 
class Meta:
abstract = True
 
class A(C, F):
atributo_a = models.CharField(u"Atributo de A", max_length=250)
 
class B(C, F):
atributo_b = models.CharField(u"Atributo de B", max_length=250)

Com essa alteração fazemos com que de F possamos acessar B da seguinte forma:
123



Um abraço, e desejo a todos:
Que todos tenham prósperos projeto,
Que seus testes funcionem,
Que programem bastante e se divirtam nessas ferias.
Ah sim, e um feliz ano novo e feliz natal… essas trivialidades de sempre…

Comentários

Postagens mais visitadas deste blog

Rails CanCan

Meus insights mais valiosos sobre criptomoedas para 2018 e além

Como pegar a senha do Whatsapp de um Android ou Iphone