Reputation: 9502
Updated question, see below
I'm starting a new project and I would like to experiment with components based architecture (I chose PyProtocols). It's a little program to display and interract with realtime graphics.
I started by designing the user input components:
Now I would like to define interfaces to filter those outputs (smooth, jitter, invert, etc...).
My first approach was to create an InputFilter interface, that had different filter methods for each kind of output channel it was connected to... But the introduction in PyProtocols documentation clearly says that the whole interface and adapters thing is about avoiding type checking !
So my guess is that my InputFilter interfaces should look like this:
Then I could have a connect() method in the I*Ouptut interfaces, that could magically adapt my filters and use the one appropriate for the type of output.
I tried to implement that, and it kind of works:
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
Now I can adapt my filters to the type of output:
filter = InvertInputFilter()
single_filter = IInputFilter(filter) # noop
sequence_filter = ISequenceInputFilter(filter) # creates an InvertSequenceInputFilter instance
single_filter and sequence_filter have the correct behaviors and produce single and sequence data types. Now if I define a new InputFilter type on the same model, I get errors like this:
TypeError: ('Ambiguous adapter choice', <class 'InvertSequenceInputFilter'>, <class 'SomeOtherSequenceInputFilter'>, 1, 1)
I must be doing something terribly wrong, is my design even correct ? Or maybe am I missing the point on how to implement my InputFilterS ?
Update 2
I understand I was expecting a little too much magic here, adapters don't type check the objects they are adapting and just look at the interface they provide, which now sounds normal to me (remember I'm new to these concepts !).
So I came up with a new design (stripped to the bare minimum and omitted the dict interfaces):
class IInputFilter(Interface):
def read():
pass
def connect(src):
pass
class ISingleInputFilter(Interface):
def read_single():
pass
class ISequenceInputFilter(Interface):
def read_sequence():
pass
So IInputFilter is now a sort of generic component, the one that is actually used, ISingleInputFilter and ISequenceInputFilter provide the specialized implementations. Now I can write adapters from the specialized to the generic interfaces:
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
Now I write my InvertInputFilter like this:
class InvertInputFilter(object):
advise(
instancesProvide=[
ISingleInputFilter,
ISequenceInputFilter
]
)
def read_single(self):
# Return single value inverted
def read_sequence(self):
# Return sequence of inverted values
And to use it with the various output types I would do:
filter = InvertInputFilter()
single_filter = SingleInputFilterAsInputFilter(filter)
sequence_filter = SequenceInputFilterAsInputFilter(filter)
But, again, this fails miserably with the same kind of error, and this time it's triggered directly by the InvertInputFilter definition:
TypeError: ('Ambiguous adapter choice', <class 'SingleInputFilterAsInputFilter'>, <class 'SequenceInputFilterAsInputFilter'>, 2, 2)
(the error disapears as soon as I put exactly one interface in the class' instancesProvide clause)
Update 3
After some discussion on the PEAK mailing list, it seems that this last error is due to a design flaw in PyProtocols, that does some extra checks at declaration time. I rewrote everything with zope.interface and it works perfectly.
Upvotes: 2
Views: 250
Reputation: 172309
I haven't used PyProtocols, only the Zope Component Architecture, but they are similar enough for these principles to be the same.
Your error is that you have two adapters that can adapt the same thing. You both have an averaging filter and an inversion filter. When you then ask for the filter, both are found, and you get the "ambigous adapter" error.
You can handle this by having different interfaces for averaging filters and inverting filters, but it's getting silly. In the Zope component architecture you would typically handle this case with named adapters. Each adapter gets a name, by default ''. In this case you would give the adapter names like "averaging" and "inverting", and you'd look them up with that name, so you know if you get the averaging or the inverting filter.
For the more general question, if the design makes sense or not, it's hard to tell. You having three different kinds of outputs and three different kinds of filters doesn't seem like a good idea. Perhaps you could make the sequence and dict outputs into composites of the single value output, so that each output value gets it's own object, so it can be filtered independently. That would make more sense to me.
Upvotes: 1