Abstract base class vs Multi-table Inheritance
소개
이 기사는 저자가 Slack에서 디자인 문제의 히스테리를 일으키면서 멍청한 어리석은 폭언 채팅을 정리한 것입니다.
TL;DR
Django Model의 Abstrat base class는 망설이지 않습니까?
장고 : 모델 상속 문제
예를 들어, 다음과 같은 모델 상속.
models.pyclass Player(models.Model):
name = models.CharField(max_length=128)
level = models.PositiveIntegerField(default=1)
class Meta:
# ...
# ...
class Equipment(models.Model):
name = models.CharField(max_length=128)
owner = models.ForeignKey(Player,
null=True,
on_delete=models.SET_NULL)
class Meta:
# ...
# ...
class Weapon(Equipment):
damage = models.PositiveIntegerField()
class Armor(Equipment):
defense = models.PositiveIntegerField()
알기 쉽게 RPG풍에 비유했습니다. Player
(플레이어)가 소유하는 Equipment
(장비)가 있어, Weapon
(무기)인가 Armor
Abstract base class
본래라면, Equipment
를 Abstract base class(이하, ABC)로 해 인스턴스화를 금지해야 합니다.
models.py# ...
class Equipment(models.Model):
# ...
class Meta:
abstract = True
# ...
하지만 이 방법은 다음과 같이 Player
의 장비를 하나의 QuerySet으로 정리할 수 없습니다.
InteractiveConsole>>> tirion = Player.objects.create(name='Tirion Fordring', level=120)
>>> tirion
<Player: Tirion Fordring>
>>> ashbringer = Weapon.objects.create(name="Ashbringer", owner=tirion, damage=160)
>>> lightbringer = Armor.objects.create(name="Lightbringer", owner=tirion, defense=69)
>>>
>>> tirion.equipment_set
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Player' object has no attribute 'equipment_set'
>>>
>>> tirion.weapon_set.all()
<QuerySet [<Weapon: Ashbringer>]>
>>> tirion.armor_set.all()
<QuerySet [<Armor: Lightbringer>]>
ABC를 사용하면 상속한 Concrete Model 이외는 테이블과 Manager를 만들지 않기 때문에 당연합니다.
Multi-table Inheritance
한편, 상기 models.py
로 Meta 옵션의 특별한 지정이 없으면, Multi-table Inheritance(이하, MTI)가 됩니다(뒤에서는 물론 OneToOne Relation).
InteractiveConsole>>> tirion = Player.objects.create(name='Tirion Fordring', level=120)
>>> tirion
<Player: Tirion Fordring>
>>>
>>> ashbringer = Weapon.objects.create(name="Ashbringer", owner=tirion, damage=160)
>>> frostmourne = Weapon.objects.create(name="Frostmourne", owner=tirion, damage=896)
>>> lightbringer = Armor.objects.create(name="Lightbringer", owner=tirion, defense=69)
>>> robe = Armor.objects.create(name="Devout Robe", owner=tirion, defense=89)
>>>
>>> tirion.equipment_set.all()
<QuerySet [<Equipment: Ashbringer>, <Equipment: Frostmourne>, <Equipment: Lightbringer>, <Equipment: Devout Robe>]>
>>> tirion.equipment_set.filter(weapon__isnull=False)
<QuerySet [<Equipment: Ashbringer>, <Equipment: Frostmourne>]>
>>> tirion.equipment_set.filter(armor__isnull=False)
<QuerySet [<Equipment: Lightbringer>, <Equipment: Devout Robe>]>
>>>
>>> tirion.weapon_set
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Player' object has no attribute 'weapon_set'
깨끗하게 장비를 정리할 수 있었습니다만, abstract로 해야 하는 장비 모델이 단독으로 인스턴스화해 버립니다.
아, SQL문도 늘어나는데...
django-model-utils
여기서 django-model-utils: InheritanceManager
의 등장입니다.
htps : // 기주 b. 코 m / 쟈 z 반 d / d 쟌고
h tps // d 짱고도 l-중 ls. Red d. cs. 이오 / 엔 / 아 st / 마나게 rs. html
models.pyfrom model_utils.managers import InheritanceManager
# ...
class Equipment(models.Model):
# ...
objects = InheritanceManager()
# ...
그러면 다음을 할 수 있습니다.
InteractiveConsole>>> tirion.equipment_set.select_subclasses()
<InheritanceQuerySet [<Weapon: Ashbringer>, <Weapon: Frostmourne>, <Armor: Lightbringer>, <Armor: Devout Robe>]>
>>> tirion.equipment_set.get_subclass(pk=1)
<Weapon: Ashbringer>
mixed iterable이지만 같은 Equipment
인스턴스다운 말이지?Equipment
의 인스턴스화 문제는 validation이나 assertion등으로 보충하면 어떻게든…
그리고 신경이 쓰이는 것은 무기와 방어구의 한층 더 subclass를 만들면 어떻게 될까요…
ABC의 존재의 의미란?
여기에서 암캐입니다.
Django Model의 Abstrat base class는 망설이지 않습니까?
장고 : 모델 상속 문제
예를 들어, 다음과 같은 모델 상속.
models.pyclass Player(models.Model):
name = models.CharField(max_length=128)
level = models.PositiveIntegerField(default=1)
class Meta:
# ...
# ...
class Equipment(models.Model):
name = models.CharField(max_length=128)
owner = models.ForeignKey(Player,
null=True,
on_delete=models.SET_NULL)
class Meta:
# ...
# ...
class Weapon(Equipment):
damage = models.PositiveIntegerField()
class Armor(Equipment):
defense = models.PositiveIntegerField()
알기 쉽게 RPG풍에 비유했습니다. Player
(플레이어)가 소유하는 Equipment
(장비)가 있어, Weapon
(무기)인가 Armor
Abstract base class
본래라면, Equipment
를 Abstract base class(이하, ABC)로 해 인스턴스화를 금지해야 합니다.
models.py# ...
class Equipment(models.Model):
# ...
class Meta:
abstract = True
# ...
하지만 이 방법은 다음과 같이 Player
의 장비를 하나의 QuerySet으로 정리할 수 없습니다.
InteractiveConsole>>> tirion = Player.objects.create(name='Tirion Fordring', level=120)
>>> tirion
<Player: Tirion Fordring>
>>> ashbringer = Weapon.objects.create(name="Ashbringer", owner=tirion, damage=160)
>>> lightbringer = Armor.objects.create(name="Lightbringer", owner=tirion, defense=69)
>>>
>>> tirion.equipment_set
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Player' object has no attribute 'equipment_set'
>>>
>>> tirion.weapon_set.all()
<QuerySet [<Weapon: Ashbringer>]>
>>> tirion.armor_set.all()
<QuerySet [<Armor: Lightbringer>]>
ABC를 사용하면 상속한 Concrete Model 이외는 테이블과 Manager를 만들지 않기 때문에 당연합니다.
Multi-table Inheritance
한편, 상기 models.py
로 Meta 옵션의 특별한 지정이 없으면, Multi-table Inheritance(이하, MTI)가 됩니다(뒤에서는 물론 OneToOne Relation).
InteractiveConsole>>> tirion = Player.objects.create(name='Tirion Fordring', level=120)
>>> tirion
<Player: Tirion Fordring>
>>>
>>> ashbringer = Weapon.objects.create(name="Ashbringer", owner=tirion, damage=160)
>>> frostmourne = Weapon.objects.create(name="Frostmourne", owner=tirion, damage=896)
>>> lightbringer = Armor.objects.create(name="Lightbringer", owner=tirion, defense=69)
>>> robe = Armor.objects.create(name="Devout Robe", owner=tirion, defense=89)
>>>
>>> tirion.equipment_set.all()
<QuerySet [<Equipment: Ashbringer>, <Equipment: Frostmourne>, <Equipment: Lightbringer>, <Equipment: Devout Robe>]>
>>> tirion.equipment_set.filter(weapon__isnull=False)
<QuerySet [<Equipment: Ashbringer>, <Equipment: Frostmourne>]>
>>> tirion.equipment_set.filter(armor__isnull=False)
<QuerySet [<Equipment: Lightbringer>, <Equipment: Devout Robe>]>
>>>
>>> tirion.weapon_set
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Player' object has no attribute 'weapon_set'
깨끗하게 장비를 정리할 수 있었습니다만, abstract로 해야 하는 장비 모델이 단독으로 인스턴스화해 버립니다.
아, SQL문도 늘어나는데...
django-model-utils
여기서 django-model-utils: InheritanceManager
의 등장입니다.
htps : // 기주 b. 코 m / 쟈 z 반 d / d 쟌고
h tps // d 짱고도 l-중 ls. Red d. cs. 이오 / 엔 / 아 st / 마나게 rs. html
models.pyfrom model_utils.managers import InheritanceManager
# ...
class Equipment(models.Model):
# ...
objects = InheritanceManager()
# ...
그러면 다음을 할 수 있습니다.
InteractiveConsole>>> tirion.equipment_set.select_subclasses()
<InheritanceQuerySet [<Weapon: Ashbringer>, <Weapon: Frostmourne>, <Armor: Lightbringer>, <Armor: Devout Robe>]>
>>> tirion.equipment_set.get_subclass(pk=1)
<Weapon: Ashbringer>
mixed iterable이지만 같은 Equipment
인스턴스다운 말이지?Equipment
의 인스턴스화 문제는 validation이나 assertion등으로 보충하면 어떻게든…
그리고 신경이 쓰이는 것은 무기와 방어구의 한층 더 subclass를 만들면 어떻게 될까요…
ABC의 존재의 의미란?
여기에서 암캐입니다.
class Player(models.Model):
name = models.CharField(max_length=128)
level = models.PositiveIntegerField(default=1)
class Meta:
# ...
# ...
class Equipment(models.Model):
name = models.CharField(max_length=128)
owner = models.ForeignKey(Player,
null=True,
on_delete=models.SET_NULL)
class Meta:
# ...
# ...
class Weapon(Equipment):
damage = models.PositiveIntegerField()
class Armor(Equipment):
defense = models.PositiveIntegerField()
# ...
class Equipment(models.Model):
# ...
class Meta:
abstract = True
# ...
>>> tirion = Player.objects.create(name='Tirion Fordring', level=120)
>>> tirion
<Player: Tirion Fordring>
>>> ashbringer = Weapon.objects.create(name="Ashbringer", owner=tirion, damage=160)
>>> lightbringer = Armor.objects.create(name="Lightbringer", owner=tirion, defense=69)
>>>
>>> tirion.equipment_set
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Player' object has no attribute 'equipment_set'
>>>
>>> tirion.weapon_set.all()
<QuerySet [<Weapon: Ashbringer>]>
>>> tirion.armor_set.all()
<QuerySet [<Armor: Lightbringer>]>
>>> tirion = Player.objects.create(name='Tirion Fordring', level=120)
>>> tirion
<Player: Tirion Fordring>
>>>
>>> ashbringer = Weapon.objects.create(name="Ashbringer", owner=tirion, damage=160)
>>> frostmourne = Weapon.objects.create(name="Frostmourne", owner=tirion, damage=896)
>>> lightbringer = Armor.objects.create(name="Lightbringer", owner=tirion, defense=69)
>>> robe = Armor.objects.create(name="Devout Robe", owner=tirion, defense=89)
>>>
>>> tirion.equipment_set.all()
<QuerySet [<Equipment: Ashbringer>, <Equipment: Frostmourne>, <Equipment: Lightbringer>, <Equipment: Devout Robe>]>
>>> tirion.equipment_set.filter(weapon__isnull=False)
<QuerySet [<Equipment: Ashbringer>, <Equipment: Frostmourne>]>
>>> tirion.equipment_set.filter(armor__isnull=False)
<QuerySet [<Equipment: Lightbringer>, <Equipment: Devout Robe>]>
>>>
>>> tirion.weapon_set
Traceback (most recent call last):
File "<console>", line 1, in <module>
AttributeError: 'Player' object has no attribute 'weapon_set'
from model_utils.managers import InheritanceManager
# ...
class Equipment(models.Model):
# ...
objects = InheritanceManager()
# ...
>>> tirion.equipment_set.select_subclasses()
<InheritanceQuerySet [<Weapon: Ashbringer>, <Weapon: Frostmourne>, <Armor: Lightbringer>, <Armor: Devout Robe>]>
>>> tirion.equipment_set.get_subclass(pk=1)
<Weapon: Ashbringer>
여기에서 암캐입니다.
metaclass=abc.ABCMeta
하고 @abstractmethod
사용하여 다중 상속(Multiple-inheritance)하면 좋지요? 깔끔하고 충돌도 없을 것 같고 Proxy는 뭔가 이상하고 ... 요약
import abc
그리고 Interface는 어떻습니까? InheritanceManager
하지만 아직 사용할 수 없다… etc.
Human.objects.all()
로 인종·국가·성 기호에 관계없이 mixed되면 세계 평화인데,Human._meta.abstract = True
나노하의 악의 근원.
References
Reference
이 문제에 관하여(Abstract base class vs Multi-table Inheritance), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/Sinclair/items/022ed8b63a34a5d1083b텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)