当从多个类组成函数时,我看到了以下两种约定:Data1
使用mixins直接向子类添加方法,而Data2
实例化完成工作的属性对象。
我认为它们是等效的,选择只是样式之一。那正确吗?还是优先于另一个?
class Data1(Reader, Processor, Writer):
def __init__(self):
Reader.__init__(self)
Processor.__init__(self)
Writer.__init__(self)
def run(self):
self.read()
self.process()
self.write()
或
class Data2:
def __init__(self):
self.reader = Reader()
self.processor = Processor()
self.writer = Writer()
def run(self):
self.reader.read()
self.processor.process()
self.writer.write()
为了阐明一个特定的示例,我有一个处理管道,其中有各种数据产品需要分别读取(Reader.read()
),处理的数据(Processor.process()
)和随后的数据产品。处理步骤需要写入db(Writer.write()
)。
更具体地说,请考虑我有多种健身数据类型:
heart-rate
表对于这些“数据产品”中的每一个,都有一个逻辑read
,process
,write
管道,我想将其捕获到一个抽象类中,然后可以用作处理未来“数据产品”的一致模板。
在这些示例中,Reader.read()
将是一个抽象类,可以读取csv,json或Web API。 Processor.process()
执行各种聚合。 Writer.write()
会将处理后的数据发送到各个地方。
鉴于此,我不确定最佳结构。
答案 0 :(得分:2)
我想避免宗教信仰,是因为您可以找到使用一个或另一个的技术理由,但是经验法则是问是A是B还是A有B? em>在前一种情况下,应在后一种情况下使用继承。
例如,彩色正方形是数字,而具有颜色。因此,它应该从图形继承并包含颜色。可能会有一些提示:子对象之一具有独立的生命周期(它可能在组合对象中使用之前就已经存在),因此毫无疑问它具有具有关系。另一方面,如果它不能单独存在(抽象类),那么毫无疑问**是a *关系。
但这意味着在不知道您称为Reader
,Writer
和Processor
以及什么数据的情况下,我无法说出我将使用哪种模型。但是,如果Reader
和Writer
都是具有独立父成员的相同祖先类的子类,那么我将使用组合。如果它们是专门定制的类,它们共享一个共同祖先的成员,那么它更多的是 is 操作,而我会使用继承。
规则是,在可能的情况下,您应该尊重真实对象的语义。毕竟,在深入的代码执行中,使用继承还是复合都没有关系。
顺便说一句,上面讨论的是一般继承与构成问题。严格来说,mixin是一种特殊情况,因为mixin不应保持任何状态,而只能添加方法,因此通常是抽象的。在Python中,mixin是通过继承实现的,但是其他语言可能具有其他实现。但是在Python中,它们是一个典型的例子,它不一定是是关系,但确实使用继承。
答案 1 :(得分:1)
使用超类的时间较短(如果正确初始化了基数):
class Data1(Reader, Processor, Writer):
def __init__(self):
super().__init__()
def run(self):
self.read()
self.process()
self.write()
但是许多人发现合成版本更易于使用,因为您无需遍历继承树来查找实现/重写方法的位置。
Wikipedia关于该主题的较长文章非常值得阅读:https://en.wikipedia.org/wiki/Composition_over_inheritance
附录:您的.run()
方法可能会更好地实现为
self.write(self.process(self.read()))
然后将其设置为函数会更容易:
def run(reader, processor, writer):
return writer.write(processor.process(reader.read()))
或例如记录:
def run(reader, processor, writer):
for data in reader.read():
log.debug("Read data: %r", data)
for output_chunk in processor.process(data):
log.debug("processed %r and got %r", data, output_chunk)
writer.write(output_chunk)
log.debug("wrote %r", output_chunk)
并调用它:
run(Reader(), Processor(), Writer())
假设读者正在产生数据,这可能会更加高效,并且为其编写单元测试也非常容易。
最后:您不需要Reader
作为csv,json或Web API读取器类的抽象基类。来自Java / C ++的人们倾向于将类和类型以及子类与子类型混合在一起。 {p>中的reader
参数的Python类型
def run(reader, processor, writer):
是∀τ≤{read:NONE→DATA},即具有.read(..)
的对象类型的所有子类型t都为NONE(None
的类型),并返回DATA类型(此处未指定)的值。例如。标准file
对象具有这种类型,可以直接传递,而不必编写带有大量样板的包装器类FileReader
。顺便说一句,这就是为什么我认为向Python添加功能不足的类型语言是一件很糟糕的事情,但是在这一点上,我意识到我正在偏离主题;-)