Вопрос о разработке компонентов Noob - PullRequest
3 голосов
/ 10 июля 2009

Обновленный вопрос, см. Ниже

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

Я начал с разработки компонентов пользовательского ввода:

  • IInputDevice - например, мышь, клавиатура и т. д. ... У 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?

Обновление 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, и он отлично работает.

1 Ответ

1 голос
/ 10 июля 2009

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

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

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

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

...