Обновленный вопрос, смотрите нижекомпоненты Noob дизайн вопрос
Я начинаю новый проект, и я хотел бы экспериментировать с компонентами архитектуры на основе (я выбрал PyProtocols). Это небольшая программа для отображения и перехвата графики в реальном времени.
Я начал проектирование входных компонентов пользователя:
- IInputDevice - например, мышь, клавиатура, и т.д. ... An InputDevice может иметь один или несколько выходных каналов:
- IOutput - выходной канал, содержащий одно значение (например, значение MIDI ползуна)
- ISequenceOutput - выходной канал, содержащий последовательность значений (например, 2 целых чисел, представляющих позицию мыши)
- IDictOutput - выходной канал, содержащий именованные значения (например, состояние каждой клавиши клавиатуры, индексированные с помощью символов клавиатуры)
Теперь я хотел бы определить интерфейсы для фильтрации этих выходов (плавный, дрожащий, инвертированный и т. Д.).
Мой первый подход заключался в создании интерфейса InputFilter, который имел разные методы фильтрации для каждого вида выходного канала, к которому он был подключен ... Но введение в документацию PyProtocols ясно говорит о том, что весь интерфейс и адаптеры - это обход проверка типов!
Так что я думаю, что мои InputFilter интерфейсы должны выглядеть следующим образом:
- IInputFilter - фильтры IOutput
- ISequenceInputFilter - фильтры ISequenceOutput
- IDictInputFilter - фильтры IDictOutput
Тогда у меня мог бы быть метод connect() в интерфейсах I * Ouptut, который мог бы волшебным образом адаптировать мои фильтры и использовать тот, который подходит для типа вывода.
Я пытался осуществить это, и это своего рода работы:
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?
Update 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
И использовать его с различными типами выходных я хотел бы сделать:
Но, опять же, это с треском проваливается с той же ошибкой, и на этот раз оно запускается непосредственно по определению InvertInputFilter:
TypeError: ('Ambiguous adapter choice', <class 'SingleInputFilterAsInputFilter'>, <class 'SequenceInputFilterAsInputFilter'>, 2, 2)
(в disapears ошибки, как только я поставил ровно один интерфейс в классе пункт instancesProvide)
Update 3
После некоторого обсуждения списка PEAK рассылки, кажется, что это последняя ошибка связана с дефектом дизайна в PyProtocols, что делает некоторые дополнительные проверки во время объявления. Я переписал все с zope.interface, и он отлично работает.
Теперь я лучше понимаю, как работают адаптеры. Именованные адаптеры недоступны в PyProtocols, и если я понимаю, что они просто синтаксический сахар, и это будет в основном такая же ситуация, как наличие другого интерфейса для каждого из моих фильтров (я хочу иметь возможность создавать фильтры в отдельных плагинах, без необходимости создавать новые интерфейсы или уникальные имена). Я не хочу делать композицию, потому что она дает возможность проверять тип, чего я хотел избежать в первую очередь. –
Я не согласен, что они синтаксический сахар. В этом случае у вас может быть один тип интерфейса вместо имени, но во многих случаях вы получаете множество компонентов, которые являются точно такими же, кроме имени, и наличие одного интерфейса для каждого компонента становится неуправляемым. Кроме того, с именованными адаптерами вы можете иметь компоненты-заводы, которые создают все компоненты из списка имен, что становится очень сложным без именованных компонентов. Наличие нескольких плагинов без дифференциации интерфейса или имени невозможно. Я не понимаю, почему композиция должна привести к проверке типа ... –
Моя вторая конструкция (см. Обновление выше) верна, моя проблема была на самом деле из-за PyProtocols: я попытался переписать все это с помощью zope.interface и оно работает. Если бы я сделал композицию, метод readFilter read() потребовал бы ветвления в зависимости от типа данных, который он получает от своего выходного канала (если тип (данные) - это dict [...] Тип elif (data) - это список [...] else [...]) –