2009-07-10 3 views
3

Обновленный вопрос, смотрите нижекомпоненты 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, и он отлично работает.

ответ

1

Я не использовал PyProtocols, только Zope Component Architecture, но они достаточно похожи, чтобы эти принципы были одинаковыми.

Ваша ошибка в том, что у вас есть два адаптера, которые могут адаптировать одно и то же. У вас есть фильтр усреднения и фильтр инверсии. Когда вы запрашиваете фильтр, оба они найдены, и вы получаете ошибку «неоднозначный адаптер».

Вы можете справиться с этим, имея различные интерфейсы для усреднения фильтров и инвертирующих фильтров, но это становится глупым. В архитектуре компонента Zope вы обычно обрабатываете этот случай с помощью именованных адаптеров. Каждый адаптер получает имя по умолчанию ''. В этом случае вы дадите имена адаптеров, такие как «усреднение» и «инвертирование», и вы будете искать их с этим именем, чтобы вы знали, получаете ли вы усреднение или инвертирующий фильтр.

Для более общего вопроса, если дизайн имеет смысл или нет, трудно сказать. У вас есть три разных вида выходов и три разных типа фильтров, которые не кажутся хорошей идеей. Возможно, вы могли бы сделать последовательности и выходы dict в композиты вывода одного значения, чтобы каждое выходное значение получало свой собственный объект, поэтому его можно фильтровать независимо. Это имело бы смысл для меня.

+0

Теперь я лучше понимаю, как работают адаптеры. Именованные адаптеры недоступны в PyProtocols, и если я понимаю, что они просто синтаксический сахар, и это будет в основном такая же ситуация, как наличие другого интерфейса для каждого из моих фильтров (я хочу иметь возможность создавать фильтры в отдельных плагинах, без необходимости создавать новые интерфейсы или уникальные имена). Я не хочу делать композицию, потому что она дает возможность проверять тип, чего я хотел избежать в первую очередь. –

+0

Я не согласен, что они синтаксический сахар. В этом случае у вас может быть один тип интерфейса вместо имени, но во многих случаях вы получаете множество компонентов, которые являются точно такими же, кроме имени, и наличие одного интерфейса для каждого компонента становится неуправляемым. Кроме того, с именованными адаптерами вы можете иметь компоненты-заводы, которые создают все компоненты из списка имен, что становится очень сложным без именованных компонентов. Наличие нескольких плагинов без дифференциации интерфейса или имени невозможно. Я не понимаю, почему композиция должна привести к проверке типа ... –

+0

Моя вторая конструкция (см. Обновление выше) верна, моя проблема была на самом деле из-за PyProtocols: я попытался переписать все это с помощью zope.interface и оно работает. Если бы я сделал композицию, метод readFilter read() потребовал бы ветвления в зависимости от типа данных, который он получает от своего выходного канала (если тип (данные) - это dict [...] Тип elif (data) - это список [...] else [...]) –