9

コードの設計についてアドバイスを求めています。

序章

いくつかのクラスがあり、それぞれが 1 つのファイル タイプを表します。たとえば、MediaImageFile、MediaAudioFile、ジェネリック (および基本クラス) MediaGenericFile です。

各ファイルには、Master と Version の 2 つのバリアントがあるため、これらのクラスを作成して、特定の動作を定義しました。編集:バージョンは、マスター ファイルのサイズ変更/クロップ/トリミング/その他のバリアントを表します。主にプレビュー用に使用されます。

編集: 動的に実行したい理由は、このアプリが再利用可能 (Django アプリ) であるため、元のコードを変更せずに他の MediaGenericFile サブクラスを簡単に実装できるはずだからです。

私がしたいこと

  1. まず第一に、ユーザーは元のコードに影響を与えることなく、独自の MediaGenericFile サブクラスを登録できる必要があります。

  2. ファイルがバージョンかマスターかは、ファイル名から簡単に (1 つの正規表現で) 認識できます。

    /path/to/master.jpg                   -- master
    /path/to/.versions/master_version.jpg -- version
    
  3. マスター/バージョン クラスは、ファイル名など、MediaGenericFile のいくつかのメソッド/プロパティを使用します (新しいバージョンを生成するにはファイル名を知る必要があります)。

  4. MediaGenericFile は、LazyFile オブジェクトである LazyFile を拡張します。

今、それをまとめる必要があります…</p>

中古デザイン

「バージョン」機能のコーディングを開始する前に、拡張子に応じて適切なファイル タイプ クラスを返すファクトリ クラス MediaFile がありました。

>>> MediaFile('path/to/image.jpg')
<<< <MediaImageFile 'path/to/image.jpg'>

クラス Master と Version は、MediaGenericFile などのメソッドと属性を使用する新しいメソッドを定義します。

アプローチ1

1 つのアプローチは、マスター (またはバージョン) と MediaGenericFile (またはサブクラス) を継承する新しい型を動的に作成することです。

class MediaFile(object):
    def __new__(cls, *args, **kwargs):
        ...  # decision about klass
        if version:
            bases = (Version, klass)
            class_name = '{0}Version'.format(klass.__name__)
        else:
            bases = (Master, klass)
            class_name = '{0}Master'.format(klass.__name__)

        new_class = type(class_name, bases, {})
        ...
        return new_class(*args, **kwargs)

アプローチ 2

2 番目のアプローチは、Master/Version でメソッド 'contribute_to_instance' を作成し、new_class を作成した後にそれを呼び出すことですが、これは思ったよりもトリッキーです。

classs Master(object):
    @classmethod
    def contribute_to_instance(cls, instance):
        methods = (...)
        for m in methods:
            setattr(instance, m, types.MethodType(getattr(cls, m), instance))

class MediaFile(object):
    def __new__(*args, **kwargs):
        ...  # decision about new_class
        obj = new_class(*args, **kwargs)
        if version:
            version_class = Version
        else:
            version_class = Master

        version_class.contribute_to_instance(obj)
        ...
        return obj

ただし、これは機能しません。マスター/バージョンのメソッドの呼び出しにはまだ問題があります。

質問

この多重継承を実装する良い方法は何でしょうか?

この問題はどのように呼ばれますか? :)私はいくつかの解決策を見つけようとしていましたが、この問題に名前を付ける方法がわかりません。

前もって感謝します!

回答への注意

広告ラースマン

私の場合、比較とインスタンスチェックは問題になりません。

  1. とにかく比較は再定義されます

    class MediaGenericFile(object):
        def __eq__(self, other):
            return self.name == other.name
    
  2. isinstance(MediaGenericFileVersion, instance) をチェックする必要はありません。isinstance(MediaGenericFile, instance) と isinstance(Version, instance) を使用していますが、どちらも期待どおりに動作します。

それにもかかわらず、インスタンスごとに新しいタイプを作成することは、かなりの欠陥のように思えます。

さて、メタクラスで両方のバリエーションを動的に作成してから、次のように使用できます。

>>> MediaGenericFile.version_class
<<< <class MediaGenericFileVersion>
>>> MediaGenericFile.master_class
<<< <class MediaGenericFileMaster>

その後:

class MediaFile(object):
    def __new__(cls, *args, **kwargs):
        ...  # decision about klass
        if version:
            attr_name = 'version_class'
        else:
            attr_name = 'master_class'

    new_class = getattr(klass, attr_name)
    ...
    return new_class(*args, **kwargs)

最終的解決

最後に、デザインパターンはファクトリークラスです。MediaGenericFile サブクラスは静的に型付けされ、ユーザーは独自に実装および登録できます。マスター/バージョン バリアントはメタクラスで動的に作成され (いくつかの mixin から結合されて)、 larsmansによって言及された危険を回避するために「キャッシュ」に格納されます。

みんなの提案に感謝します。最後に、メタクラスの概念を理解しました。まあ、少なくとも私はそれを理解していると思います。送信元マスターをプッシュ…</p>

4

5 に答える 5

4

どれだけ動的にしたいのかわかりませんが、「ファクトリーパターン」(ここではクラスファクトリーを使用)を使用すると、かなり読みやすく理解しやすく、必要なことができる場合があります。これはベースとして機能するMediaFactory可能性があります...よりスマートになる可能性があり、ハードコーディングMediaFactoryMasterなどの代わりに、他の複数のクラスを登録できます...

class MediaFactory(object):

    __items = {}

    @classmethod
    def make(cls, item):
        return cls.__items[item]

    @classmethod
    def register(cls, item):
        def func(kls):
            cls.__items[item] = kls
            return kls
        return func

class MediaFactoryMaster(MediaFactory, Master): pass
class MediaFactoryVersion(MediaFactory, Version): pass

class MediaFile(object):
    pass

@MediaFactoryMaster.register('jpg') # adapt to take ['jpg', 'gif', 'png'] ?
class MediaFileImage(MediaFile):
    pass

@MediaFactoryVersion.register('mp3') # adapt to take ['mp3', 'ogg', 'm4a'] ?
class MediaFileAudio(MediaFile):
    pass

その他の可能な MediaFactory.make

@classmethod
def make(cls, fname):
    name, ext = somefunc(fname)
    kls = cls.__items[ext]
    other = Version if Version else Master
    return type('{}{}'.format(kls.__name__,other.__name__), (kls, other), {})
于 2012-07-21T11:48:37.770 に答える
4

でクラスを構築する最初のアプローチには絶対に反対することをお勧めし__new__ます。それに関する問題は、インスタンスごとに新しいタイプを作成することです。これにより、オーバーヘッドが発生し、さらに悪いことに、タイプ比較が失敗します。

>>> Ham1 = type("Ham", (object,), {})
>>> Ham2 = type("Ham", (object,), {})
>>> Ham1 == Ham2
False
>>> isinstance(Ham1(), Ham2)
False
>>> isinstance(Ham2(), Ham1)
False

クラスが完全に同一に見える可能性があるため、これは最小の驚きの原則に違反します。

>>> Ham1
<class '__main__.Ham'>
>>> Ham2
<class '__main__.Ham'>

ただし、モジュール レベルの外部でクラスを構築する場合は、アプローチ 1 を適切に機能させることができますMediaFile

classes = {}
for klass in [MediaImageFile, MediaAudioFile]:
    for variant in [Master, Version]:
        # I'd actually do this the other way around,
        # making Master and Version mixins
        bases = (variant, klass)
        name = klass.__name__ + variant.__name__
        classes[name] = type(name, bases, {})

次に、 でMediaFile.__new__必要なクラスを名前で検索しclassesます。(または、新しく構築されたクラスを ではなくモジュールに設定しますdict。)

于 2012-07-21T11:16:23.490 に答える
2

継承を使用していないのに、なぜ遊んでいるの__new__ですか?

class GenericFile(File):
    """Base class"""

class Master(object):
    """Master Mixin"""

class Versioned(object):
    """Versioning mixin"""

class ImageFile(GenericFile):
    """Image Files"""

class MasterImage(ImageFile, Master):
    """Whatever"""

class VersionedImage(ImageFile, Versioned):
    """Blah blah blah"""

...

ただし、なぜこれを行っているのかは明らかではありません。ここには奇妙なコードの匂いがあると思います。すべてを機能させるために、コード全体で 多数のクラスとチェックを行うよりも、一貫したインターフェイス (ダックタイピング) を備えた少数のクラスをお勧めします。isinstance

おそらく、コードでやりたいことで質問を更新することができ、人々は実際のパターンを特定するか、より慣用的な解決策を提案するのを助けることができます.

于 2012-07-21T11:42:15.467 に答える
1

インスタンスごとに新しいクラスを作成する必要はありません。で作成するときに新しいクラスを__new__作成しないでください__metaclass__。baseまたはbase_moduleでメタクラスを定義します。2つの「バリアント」サブクラスは、それらの一般的な親のクラス属性として簡単に保存され、__new__独自のルールに従ってファイル名を調べて、返すサブクラスを決定します。

__new__コンストラクターの呼び出し中に「指定された」クラス以外のクラスが返されることに 注意してください。withingから呼び出すための手順を実行する必要がある場合があります__init____new__

サブクラスは次のいずれかを行う必要があります。

  1. 見つけられる工場または親に自分自身を「登録」する
  2. edを実行importしてから、親またはファクトリに次の再帰検索でそれらを見つけてもらいますcls.__subclasses(作成ごとに1回実行する必要がある場合がありますが、ファイル処理の問題ではない可能性があります)
  3. 「setuptools」entry_pointsタイプのツールを使用して検出されますが、ユーザーによるより多くの労力と調整が必要です。
于 2012-07-21T13:50:50.730 に答える
0

あなたが尋ねるべき OOD の質問は、「私が提案した継承のさまざまなクラスは、何らかのプロパティ共有していますか?」ということです。

継承の目的は、インスタンスが自然に共有する共通のデータまたはメソッドを共有することです。どちらもファイルであるということ以外に、画像ファイルと音声ファイルの共通点は何ですか? メタファを拡張したい場合AudioFile.view()は、オーディオ データのパワー スペクトルの視覚化などを提示できる可能性がありますが、それ以上のImageFile.listen()意味はありません。

あなたの質問は、この言語に依存しない概念的な問題を回避し、オブジェクト ファクトリの Python 依存のメカニズムを支持していると思います。ここで継承の適切なケースがあるとは思わないか、Media オブジェクトが共有する必要がある一般的な機能を説明できていません。

于 2012-07-21T11:42:53.517 に答える