Полезная семантика публикации-подписки - PullRequest
2 голосов
/ 18 января 2011

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

Я исследовал свои книги и Интернет для работы, проделанной в Python и Delphi для публикации / подписки, и не был доволен результатами. Проекты основывались на сигнатурах функций или растровых изображениях или слотах для фильтрации сообщений или определения того, что следует доставлять кому, и были либо слишком жесткими (привязаны к серверу обмена сообщениями), либо слишком разнородными (каждый может подписаться на что угодно ).

Я не хочу писать свои собственные. Я хочу найти что-то, что уже хорошо спроектировано, обсуждено и проверено на практике.

Сегодня я реализовал дизайн в Delphi Pascal (потому что Delphi был тем, что мне было нужно сначала). Диспетчеризация по типу аргумента, как это делает API, не является оригинальной идеей (объясняется шаблон Design Patterns Visitor), и я думаю, что я видел нечто подобное раньше (но я не помнишь где; талигент?). Суть в том, что подписка, фильтрация и рассылка осуществляется по системе типов.

unit JalSignals;
//  A publish/subscribe mechanism.    
//  1. Signal payloads are objects, and their class is their signal type.
//  2. Free is called on the payloads after they have been delivered.    
//  3. Members subscribe by providing a callback method (of object).
//  4. Members may subscribe with the same method to different types of signals.
//  5. A member subscribes to a type, which means that all signals
//     with payloads of that class or any of its subclasses will be delivered
//     to the callback, with one important exception    
//  6. A forum breaks the general class hierarchy into independent branches.    
//     A signal will not be delivered to members subscribed to classes that    
//     are not in the branch.    
//  7. This is a GPL v3 design.
interface
uses
  SysUtils;
type
  TSignal = TObject;
  TSignalType = TClass;
  TSignalAction = (soGo, soStop);
  TCallback = function(signal :TSignal) :TSignalAction of object;

  procedure signal(payload: TSignal);

  procedure subscribe(  callback :TCallback; atype :TSignalType);
  procedure unsubscribe(callback :TCallback; atype :TSignalType = nil); overload;
  procedure unsubscribe(obj      :TObject;   atype :TSignalType = nil); overload;

  procedure openForum( atype :TSignalType);
  procedure closeForum(atype :TSignalType);

«Обратные вызовы» в вышеприведенном подобны связанным методам в Python.

Полный исходный код для реализации Delphi: здесь :

Это реализация в Python. Я изменил названия клавиш, потому что signal и message уже слишком перегружены. В отличие от реализации Delphi, результаты, включая исключения, собираются и возвращаются сигнализатору в виде списка.

"""
  A publish/subscribe mechanism.

  1. Signal payloads are objects, and their class is their signal type.
  2. Free is called on the payloads after they have been delivered.
  3. Members subscribe by providing a callback method (of object).
  4. Members may subscribe with the same method to different types of signals.
  5. A member subscribes to a type, which means that all signals
     with payloads of that class or any of its subclasses will be delivered
     to the callback, with one important exception:
  6. A forum breaks the general class hierarchy into independent branches.
     A signal will not be delivered to members subscribed to classes that
     are not in the branch.
"""

__all__ = ['open_forum', 'close_forum', 'announce',
           'subscribe', 'unsubscribe'
           ]

def _is_type(atype):
    return issubclass(atype, object)

class Sub(object):
    def __init__(self, callback, atype):
        assert callable(callback)
        assert issubclass(atype, object)
        self.atype = atype
        self.callback = callback

__forums = set()
__subscriptions = []

def open_forum(forum):
    assert issubclass(forum, object)
    __forums.add(forum)

def close_forum(forum):
    __forums.remove(forum)

def subscribe(callback, atype):
    __subscriptions.append(Sub(callback, atype))

def unsubscribe(callback, atype=None):
    for i, sub in enumerate(__subscriptions):
        if sub.callback is not callback:
            continue
        if atype is None or issubclass(sub.atype, atype):
            del __subscriptions[i]

def _boundary(atype):
    assert _is_type(atype)
    lower = object
    for f in __forums:
        if (issubclass(atype, f)
            and issubclass(f, lower)):
            lower = f
    return lower

def _receivers(news):
    bound = _boundary(type(news))
    for sub in __subscriptions:
        if not isinstance(news, sub.atype):
            continue
        if not issubclass(sub.atype, bound):
            continue
        yield sub

def announce(news):
    replies = []
    for sub in _receivers(news):
        try:
            reply = sub.callback(news)
            replies.append(reply)
        except Exception as e:
            replies.append(e)
    return replies

if __name__ == '__main__':
    i = 0
    class A(object):
        def __init__(self):
            global i
            self.msg = type(self).__name__ + str(i)
            i += 1

    class B(A): pass
    class C(B): pass

    assert _is_type(A)
    assert _is_type(B)
    assert _is_type(C)

    assert issubclass(B, A)
    assert issubclass(C, B)

    def makeHandler(atype):
        def handler(s):
            assert isinstance(s, atype)
            return 'handler' + atype.__name__ + ' got ' + s.msg
        return handler

    handleA = makeHandler(A)
    handleB = makeHandler(B)
    handleC = makeHandler(C)

    def failer(s):
        raise Exception, 'failed on' + s.msg

    assert callable(handleA) and callable(handleB) and callable(handleC)

    subscribe(handleA, A)
    subscribe(handleB, B)
    subscribe(handleC, C)
    subscribe(failer, A)

    assert _boundary(A) is object
    assert _boundary(B) is object
    assert _boundary(C) is object

    print announce(A())
    print announce(B())
    print announce(C())

    print
    open_forum(B)

    assert _boundary(A) is object
    assert _boundary(B) is B
    assert _boundary(C) is B
    assert issubclass(B, B)

    print announce(A())
    print announce(B())
    print announce(C())

    print
    close_forum(B)
    print announce(A())
    print announce(B())
    print announce(C())

Вот причины моего поиска:

  1. Я прошёл несколько тысяч строк кода Delphi, которые мне нужно поддерживать. Они используют шаблон Observer для разъединения MVC, но все по-прежнему очень взаимосвязано, потому что зависимости между наблюдателями и субъектами слишком явные.
  2. Я изучал PyQt4, и он убил бы меня, если бы мне пришлось щелкать, щелкать, щелкать в Qt4Designer для каждого события, которое я хочу достичь в значимом месте назначения.
  3. Тем не менее, в другом приложении для работы с личными данными мне нужно абстрагироваться от передачи и обработки событий, поскольку постоянство и пользовательский интерфейс будут различаться в зависимости от платформы, и должен быть полностью независимым.

Ссылки

Найден сам, и другие должны идти сюда

  • PybubSub использует строки для топиков и сигнатуры методов (первый сигнал определяет сигнатуру).
  • Статья в блоге FinalBuilder сообщает, что они успешно использовали систему с целочисленными структурами в качестве полезных нагрузок, в качестве сообщений и целочисленных масок для фильтрации.
  • PyDispatcher имеет минимальную документацию.
  • D-Bus был принят, среди прочего, в проектах Gnome и KDE. Доступно связывание Python .

Ответы [ 2 ]

2 голосов
/ 03 января 2013

Вы также можете попробовать DDS. Служба распространения данных - это полный стандарт для шаблонов связи, использующих семантику публикации / подписки.

2 голосов
/ 21 января 2011

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

...