我正在寻求有关设计代码的建议。
我有几个类,每个类代表一种文件类型,例如:MediaImageFile,MediaAudioFile和generic(以及基类)MediaGenericFile。
每个文件都有两个变体:Master和Version,所以我创建了这些类来定义它们的特定行为。 编辑:版本代表主文件的调整大小/裁剪/修剪/等变体。它主要用于预览。
编辑: 原因,为什么我想动态地做这个应用程序应该是可重用的(它是Django-app)因此应该很容易实现其他MediaGenericFile子类而不用修改原始代码。
首先,用户应该能够在不影响原始代码的情况下注册自己的MediaGenericFile子类。
文件是版本还是主文件很容易(一个正则表达式)可以从文件名识别。
/path/to/master.jpg -- master
/path/to/.versions/master_version.jpg -- version
Master / Version类使用MediaGenericFile的一些方法/属性,比如filename(你需要知道文件名来生成新版本)。
MediaGenericFile扩展了LazyFile,它只是懒惰的File对象。
现在我需要把它放在一起......
在开始编写'版本'功能之前,我有工厂类MediaFile,它根据扩展名返回适当的文件类型类:
>>> MediaFile('path/to/image.jpg')
<<< <MediaImageFile 'path/to/image.jpg'>
Classes Master和Version定义了使用MediaGenericFile等方法和属性的新方法。
一种方法是创建动态新类型,它继承Master(或Version)和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)
第二种方法是在Master / Version中创建方法'contribution_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
然而,这不起作用。调用Master / Version的方法仍然存在问题。
实现这种多重继承的好方法是什么?
这个问题怎么称呼? :)我试图找到一些解决方案,但我根本不知道如何命名这个问题。
提前致谢!
对我的情况进行比较和实例检查不会有问题,因为:
无论如何都要重新定义比较
class MediaGenericFile(object):
def __eq__(self, other):
return self.name == other.name
我永远不需要检查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粘合在一起)并存储在'cache'中,以避免 larsmans 提到的危险。
感谢大家的建议。最后我理解了元类概念。好吧,至少我认为我理解它。推送原始主...
答案 0 :(得分: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), {})
答案 1 :(得分:3)
我当然建议不要在__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'>
但是,如果您在MediaFile
之外的模块级别构建类,则可以使方法1正常工作:
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
中设置新构造的类。)
答案 2 :(得分: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"""
...
目前尚不清楚为什么要这样做。我觉得这里有一种奇怪的代码味道。我建议使用一致的接口(duck-typing)而不是十几个类和isinstance
检查整个代码中的更少的类,以使它全部工作。
也许您可以使用您希望在代码中执行的操作更新您的问题,人们可以帮助确定真实模式或建议更具惯用性的解决方案。
答案 3 :(得分:0)
你应该问的OOD问题是“我提议的继承的各个类别共享所有属性吗?”
继承的目的是共享实例自然具有的共同数据或方法。除了两个文件之外,图像文件和音频文件有什么共同之处?如果你真的想要扩展你的隐喻,你可以想象AudioFile.view()
可以呈现 - 例如 - 音频数据的功率谱的可视化,但是ImageFile.listen()
更没意义。
我认为你的问题是支持这种语言独立的概念问题,而不是对象工厂的Python依赖机制。我认为你没有适当的继承案例,或者你没有解释你的媒体对象需要分享的常见功能。
答案 4 :(得分:0)
您不必为每个实例创建新类。请勿在{{1}}中创建新类,并在__new__
中创建新类。在base或base_module中定义元类。这两个“变体”子类很容易保存为其genaric父类的类属性,然后__metaclass__
只根据它自己的规则查看文件名并决定返回哪个子类。
注意__new__
在构造函数调用期间返回除“指定”之外的类。 You may have to take steps to invoke __init__
from withing __new__
子类要么必须:
__new__
编辑,然后让父母或工厂通过import
的递归搜索找到它们(每次创建可能必须发生一次,但这可能不是文件处理的问题)entry_points
类型工具找到,但需要用户付出更多努力和协调