gwift-book/source/part-4-go-live/gwift/refactoring.adoc

153 lines
5.3 KiB
Plaintext
Raw Normal View History

2020-02-17 20:45:39 +01:00
== Refactoring
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
On constate que plusieurs classes possèdent les mêmes propriétés `created_at` et `updated_at`, initialisées aux mêmes valeurs. Pour gagner en cohérence, nous allons créer une classe dans laquelle nous définirons ces deux champs, et nous ferons en sorte que les classes `Wishlist`, `Item` et `Part` en héritent. Django gère trois sortes d'héritage:
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
* L'héritage par classe abstraite
* L'héritage classique
* L'héritage par classe proxy.
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
=== Classe abstraite
2015-12-23 23:03:21 +01:00
L'héritage par classe abstraite consiste à déterminer une classe mère qui ne sera jamais instanciée. C'est utile pour définir des champs qui se répèteront dans plusieurs autres classes et surtout pour respecter le principe de DRY. Comme la classe mère ne sera jamais instanciée, ces champs seront en fait dupliqués physiquement, et traduits en SQL, dans chacune des classes filles.
2020-02-17 20:45:39 +01:00
[source,python]
----
# wish/models.py
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
class AbstractModel(models.Model):
class Meta:
abstract = True
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
class Wishlist(AbstractModel):
pass
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
class Item(AbstractModel):
pass
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
class Part(AbstractModel):
pass
----
2015-12-23 23:03:21 +01:00
En traduisant ceci en SQL, on aura en fait trois tables, chacune reprenant les champs `created_at` et `updated_at`, ainsi que son propre identifiant:
2020-02-17 20:45:39 +01:00
[source,sql]
----
--$ python manage.py sql wish
BEGIN;
CREATE TABLE "wish_wishlist" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"created_at" datetime NOT NULL,
"updated_at" datetime NOT NULL
)
;
CREATE TABLE "wish_item" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"created_at" datetime NOT NULL,
"updated_at" datetime NOT NULL
)
;
CREATE TABLE "wish_part" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"created_at" datetime NOT NULL,
"updated_at" datetime NOT NULL
)
;
COMMIT;
----
=== Héritage classique
2015-12-23 23:03:21 +01:00
L'héritage classique est généralement déconseillé, car il peut introduire très rapidement un problème de performances: en reprenant l'exemple introduit avec l'héritage par classe abstraite, et en omettant l'attribut `abstract = True`, on se retrouvera en fait avec quatre tables SQL:
2020-02-17 20:45:39 +01:00
* Une table `AbstractModel`, qui reprend les deux champs `created_at` et `updated_at`
* Une table `Wishlist`
* Une table `Item`
* Une table `Part`.
A nouveau, en analysant la sortie SQL de cette modélisation, on obtient ceci:
2015-12-23 23:03:21 +01:00
2020-02-17 20:45:39 +01:00
[source,sql]
----
--$ python manage.py sql wish
BEGIN;
CREATE TABLE "wish_abstractmodel" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"created_at" datetime NOT NULL,
"updated_at" datetime NOT NULL
)
;
CREATE TABLE "wish_wishlist" (
"abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
)
;
CREATE TABLE "wish_item" (
"abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
)
;
CREATE TABLE "wish_part" (
"abstractmodel_ptr_id" integer NOT NULL PRIMARY KEY REFERENCES "wish_abstractmodel" ("id")
)
;
COMMIT;
----
2015-12-23 23:03:21 +01:00
Le problème est que les identifiants seront définis et incrémentés au niveau de la table mère. Pour obtenir les informations héritées, nous seront obligés de faire une jointure. En gros, impossible d'obtenir les données complètes pour l'une des classes de notre travail de base sans effectuer un *join* sur la classe mère.
Dans ce sens, cela va encore... Mais imaginez que vous définissiez une classe `Wishlist`, de laquelle héritent les classes `ChristmasWishlist` et `EasterWishlist`: pour obtenir la liste complètes des listes de souhaits, il vous faudra faire une jointure **externe** sur chacune des tables possibles, avant même d'avoir commencé à remplir vos données. Il est parfois nécessaire de passer par cette modélisation, mais en étant conscient des risques inhérents.
2020-02-17 20:45:39 +01:00
=== Classe proxy
2015-12-23 23:03:21 +01:00
Lorsqu'on définit une classe de type **proxy**, on fait en sorte que cette nouvelle classe ne définisse aucun nouveau champ sur la classe mère. Cela ne change dès lors rien à la traduction du modèle de données en SQL, puisque la classe mère sera traduite par une table, et la classe fille ira récupérer les mêmes informations dans la même table: elle ne fera qu'ajouter ou modifier un comportement dynamiquement, sans ajouter d'emplacements de stockage supplémentaires.
Nous pourrions ainsi définir les classes suivantes:
2020-02-17 20:45:39 +01:00
[source,python]
----
# wish/models.py
class Wishlist(models.Model):
name = models.CharField(max_length=255)
description = models.CharField(max_length=2000)
expiration_date = models.DateField()
@staticmethod
def create(self, name, description, expiration_date=None):
wishlist = Wishlist()
wishlist.name = name
wishlist.description = description
wishlist.expiration_date = expiration_date
wishlist.save()
return wishlist
class ChristmasWishlist(Wishlist):
class Meta:
proxy = True
@staticmethod
def create(self, name, description):
christmas = datetime(current_year, 12, 31)
w = Wishlist.create(name, description, christmas)
w.save()
class EasterWishlist(Wishlist):
class Meta:
proxy = True
@staticmethod
def create(self, name, description):
expiration_date = datetime(current_year, 4, 1)
w = Wishlist.create(name, description, expiration_date)
w.save()
----