【问题标题】:Noob components design question菜鸟组件设计问题
【发布时间】:2010-11-09 14:13:05
【问题描述】:

更新问题,见下文

我正在开始一个新项目,我想试验基于组件的架构(我选择了PyProtocols)。这是一个显示实时图形并与之交互的小程序。

我从设计用户输入组件开始:

  • IInputDevice - 例如鼠标、键盘等... InputDevice 可能有一个或多个输出通道:
    • IOutput - 包含单个值(例如 MIDI 滑块的值)的输出通道
    • ISequenceOutput - 包含一系列值的输出通道(例如,代表鼠标位置的 2 个整数)
    • IDictOutput - 一个包含命名值的输出通道(例如,键盘每个键的状态,由键盘符号索引)

现在我想定义接口来过滤这些输出(平滑、抖动、反转等...)。

我的第一种方法是创建一个 InputFilter 接口,它对连接到的每种输出通道都有不同的过滤器方法...但是 PyProtocols 文档中的介绍清楚地表明整个接口和适配器是关于避免类型检查!

所以我的猜测是我的 InputFilter 接口应该是这样的:

  • IInputFilter - 过滤 IOutput
  • ISequenceInputFilter - 过滤 ISequenceOutput
  • IDictInputFilter - 过滤 IDictOutput

然后我可以在 I*Ouptut 接口中有一个 connect() 方法,它可以神奇地调整我的过滤器并使用适合输出类型的过滤器。

我试图实现它,它有点工作:

class InputFilter(object):
    """
    Basic InputFilter implementation. 
    """

    advise(
            instancesProvide=[IInputFilter],
        )

    def __init__(self):
        self.parameters = {}

    def connect(self, src):
        self.src = src

    def read(self):
        return self.src.read()


class InvertInputFilter(InputFilter):
    """
    A filter inverting single values.
    """

    def read(self):
        return -self.src.read()


class InvertSequenceInputFilter(InputFilter):
    """
    A filter inverting sequences of values.
    """

    advise(
            instancesProvide=[ISequenceInputFilter],
            asAdapterForProtocols=[IInputFilter],
        )

    def __init__(self, ob):
        self.ob = ob

    def read(self):
        res = [] 
        for value in self.src.read():
            res.append(-value)
        return res

现在我可以根据输出类型调整过滤器:

filter = InvertInputFilter()
single_filter = IInputFilter(filter)           # noop
sequence_filter = ISequenceInputFilter(filter) # creates an InvertSequenceInputFilter instance

single_filter 和 sequence_filter 具有正确的行为并产生单一和序列数据类型。现在如果我在同一个模型上定义一个新的 InputFilter 类型,我会得到这样的错误:

TypeError: ('Ambiguous adapter choice', <class 'InvertSequenceInputFilter'>, <class 'SomeOtherSequenceInputFilter'>, 1, 1)

我一定做错了什么,我的设计是否正确?或者我是否错过了如何实现我的 InputFilterS 的要点?

更新 2

我知道我在这里期待太多的魔法,适配器不会检查它们正在适应的对象,而只是查看它们提供的接口,这对我来说现在听起来很正常(记住我对这些概念很陌生!)。

所以我想出了一个新设计(精简到最低限度并省略了 dict 接口):

class IInputFilter(Interface):

    def read():
        pass

    def connect(src):
        pass


class ISingleInputFilter(Interface):        

    def read_single():
        pass


class ISequenceInputFilter(Interface):

    def read_sequence():
        pass

所以 IInputFilter 现在是一种通用组件,实际使用的组件,ISingleInputFilter 和 ISequenceInputFilter 提供了专门的实现。现在我可以编写从专用接口到通用接口的适配器:

class SingleInputFilterAsInputFilter(object):

    advise(
            instancesProvide=[IInputFilter],
            asAdapterForProtocols=[ISingleInputFilter],
        )

    def __init__(self, ob):
        self.read = ob.read_single


class SequenceInputFilterAsInputFilter(object):

    advise(
            instancesProvide=[IInputFilter],
            asAdapterForProtocols=[ISequenceInputFilter],
        )

    def __init__(self, ob):
        self.read = ob.read_sequence

现在我这样写我的 InvertInputFilter:

class InvertInputFilter(object):

    advise(
            instancesProvide=[
                    ISingleInputFilter, 
                    ISequenceInputFilter
                ]
        )

    def read_single(self):
        # Return single value inverted

    def read_sequence(self):
        # Return sequence of inverted values 

并将它与我会做的各种输出类型一起使用:

filter = InvertInputFilter()
single_filter = SingleInputFilterAsInputFilter(filter)
sequence_filter = SequenceInputFilterAsInputFilter(filter)

但是,同样的错误,这又一次惨败,这一次它是由 InvertInputFilter 定义直接触发的:

TypeError: ('Ambiguous adapter choice', <class 'SingleInputFilterAsInputFilter'>, <class 'SequenceInputFilterAsInputFilter'>, 2, 2)

(只要我在类的instancesProvide 子句中放置一个接口,错误就会消失)

更新 3

在对 PEAK 邮件列表进行了一些讨论之后,似乎最后一个错误是由于 PyProtocols 中的设计缺陷造成的,它在声明时会进行一些额外的检查。我用 zope.interface 重写了所有内容,并且效果很好。

【问题讨论】:

    标签: python interface protocols


    【解决方案1】:

    我没有使用 PyProtocols,只使用了 Zope 组件架构,但它们非常相似,因此这些原则是相同的。

    您的错误是您有两个可以适应同一事物的适配器。你们都有一个平均滤波器和一个反演滤波器。然后,当您请求过滤器时,两者都找到了,您会收到“模糊适配器”错误。

    您可以通过使用不同的接口来平均过滤器和反转过滤器来处理这个问题,但它变得很愚蠢。在 Zope 组件架构中,您通常会使用命名适配器来处理这种情况。每个适配器都有一个名称,默认为 ''。在这种情况下,您可以给适配器命名,如“平均”和“反相”,然后使用该名称查找它们,这样您就知道是平均滤波器还是反相滤波器。

    对于更一般的问题,设计是否有意义,很难说。您拥有三种不同类型的输出和三种不同类型的过滤器似乎不是一个好主意。也许您可以将序列和字典输出组合成单值输出,这样每个输出值都有自己的对象,因此可以独立过滤。这对我来说更有意义。

    【讨论】:

    • 谢谢,现在我更好地了解了适配器的工作原理。命名适配器在 PyProtocols 中不可用,如果我理解得很好,它们只是语法糖,这与我的每个过滤器都有不同的接口基本上是相同的情况(我希望能够在单独的插件中创建过滤器,而不用打扰创建新接口或唯一名称)。我不想做组合,因为它会产生类型检查,这是我一开始就想避免的。
    • 我不同意它们是语法糖。在这种情况下,您可以使用一种接口类型而不是名称,但在许多情况下,您最终会拥有许多除了名称之外完全相同的组件,并且每个组件都有一个接口会变得不规则。此外,使用命名适配器,您可以让组件工厂从名称列表中创建所有组件,如果没有命名组件,这将变得非常棘手。拥有多个插件而不区分接口或名称是不可能的。我不明白为什么组合必须导致类型检查......
    • 我的第二个设计(见上面的更新)是正确的,我的问题实际上是由于 PyProtocols:我试图用 zope.interface 重写整个事情并且它有效。如果我进行组合,InputFilter read() 方法将需要根据它从其输出通道接收的数据类型进行分支(如果 type(data) 是 dict [...] elif type(data) is list [...]否则[...])
    • 不,合成的重点是您不必这样做,只需让界面在所有情况下都相同。基本上,你可以一直使用 dict 接口。
    • 哦,对不起,我误解了构图的意思...我想我要重新设计它,这 3 种设计增加了复杂性并且没有任何优势...谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多