Abstract base class vs Multi-table Inheritance

소개



이 기사는 저자가 Slack에서 디자인 문제의 히스테리를 일으키면서 멍청한 어리석은 폭언 채팅을 정리한 것입니다.

TL;DR



Django Model의 Abstrat base class는 망설이지 않습니까?

장고 : 모델 상속 문제



예를 들어, 다음과 같은 모델 상속.

models.py
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()

알기 쉽게 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.py
from 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의 ABC는 단지, 필드 메소드의 코드 공통화 이외 의미 없지? 동적 타입이기 때문에 Polymorphism의 의미도 없고 ...
  • Interface 만들면 metaclass=abc.ABCMeta 하고 @abstractmethod 사용하여 다중 상속(Multiple-inheritance)하면 좋지요? 깔끔하고 충돌도 없을 것 같고 Proxy는 뭔가 이상하고 ...

  • 요약


  • Multi-table Inheritance로 가자
  • import abc 그리고 Interface는 어떻습니까?
  • Django2.0에서 InheritanceManager 하지만 아직 사용할 수 없다…
  • 경험이 풍부한 분의 마사카리, 기다리고 있습니다.

  • etc.


    Human.objects.all() 로 인종·국가·성 기호에 관계없이 mixed되면 세계 평화인데,Human._meta.abstract = True 나노하의 악의 근원.



    References


  • 오브젝트 지향과 10년 싸워서 알게 된 것
  • How to query abstract-class-based objects in Django?
  • 좋은 웹페이지 즐겨찾기