Djangoのモデル継承でポリモーフィズムを実現する
Djangoのモデルクラスは継承が可能ですが、基底クラスのオブジェクトを派生クラスのオブジェクトに変換する手段がデフォルトで存在しないため、普通のオブジェクト指向プログラミングのように、派生クラスでメソッドをオーバーライドしてオブジェクトの振る舞いを変えるといったことができません。
具体例として、次のサンプルプログラムを実行してみます。
- without_polymorphic/models.py
from django.db import models class Base(models.Model): pass class Derived1(Base): pass class Derived2(Base): pass
- sample1.py
import settings from django.core import management management.setup_environ(settings) from django.db.transaction import commit_on_success from without_polymorphic import models @commit_on_success def delete_instances(): for base in models.Base.objects.all(): base.delete() @commit_on_success def create_instances(): print '='*40 print 'create_instances' print '='*40 objs = (models.Derived1(), models.Derived1(), models.Derived2(), models.Derived2()) for obj in objs: print obj obj.save() def show_instances(): print '='*40 print 'show_instances' print '='*40 for obj in models.Base.objects.all(): print obj def main(): delete_instances() create_instances() show_instances() if __name__ == '__main__': main()
- 実行結果
======================================== create_instances ======================================== Derived1 object Derived1 object Derived2 object Derived2 object ======================================== show_instances ======================================== Base object Base object Base object Base object
オブジェクト作成時はオブジェクトのクラスは派生後のクラスですが、データベースから取り出したオブジェクトのクラスは基底クラスとなっていることが分かります。これでは、派生クラスでいくらメソッドをオーバーライドしても、そのメソッドを使うことができません。
(もちろん、オブジェクト取得の部分でmodels.Base.objects.all()の代わりにmodels.Derived1.objects.all()と書けば派生クラスのオブジェクトを得ることが出来ますが、その方法で全てのオブジェクトを得るには、派生クラスを全て知っている必要があります。)
基底クラスのオブジェクトから派生クラスのオブジェクトに変換するには、django-polymorphic-modelsというライブラリを使用します。このライブラリをDjangoのアプリに追加し、次のプログラムを実行してみます。
- with_polymorphic/models.py
from django.db import models from polymorphic.models import PolymorphicMetaclass class Base(models.Model): __metaclass__ = PolymorphicMetaclass class Derived1(Base): pass class Derived2(Base): pass
- sample2.py
import settings from django.core import management management.setup_environ(settings) from django.db.transaction import commit_on_success from with_polymorphic import models @commit_on_success def delete_instances(): for base in models.Base.objects.all(): base.delete() @commit_on_success def create_instances(): print '='*40 print 'create_instances' print '='*40 objs = (models.Derived1(), models.Derived1(), models.Derived2(), models.Derived2()) for obj in objs: print obj obj.save() def show_instances(): print '='*40 print 'show_instances' print '='*40 for obj in models.Base.objects.all(): obj = obj.downcast() print obj def main(): delete_instances() create_instances() show_instances() if __name__ == '__main__': main()
最初の例との違いは、Baseクラスのメタクラスをpolymorphic.models.PolymorphicMetaclassにした点と、基底クラスのオブジェクトを派生クラスのオブジェクトに変換するために、downcasetメソッドを実行している点です。
このプログラムの実行結果は次のようになります。
======================================== create_instances ======================================== Derived1 object Derived1 object Derived2 object Derived2 object ======================================== show_instances ======================================== Derived1 object Derived1 object Derived2 object Derived2 object
この例では、派生クラスのオブジェクトを得るためにdowncastメソッドを実行していますが、基底クラスのメタクラスをPolymorphicMetaclassではなくDowncastMetaclassとすると、自動的にdowncastメソッドを実行してくれるようです。