有用的发布 - 订阅语义

时间:2011-01-18 05:25:20

标签: python algorithm delphi publish-subscribe class-hierarchy

我正在寻找对实际工作的轻量级发布 - 订阅机制的设计和实现的维基百科风格的引用。我会根据答案和评论以及我自己的研究来更新问题。

我研究了我的书和Web上用Python和Delphi完成的发布/订阅工作,并对结果不满意。设计依赖于功能签名或位图或插槽来过滤消息或决定应该向谁传递什么,并且要么限制太多(绑定到消息服务器),要么过于混乱(每个人都可以订阅任何东西)。

我不想写自己的。我想找到一些已经经过精心设计,辩论和现场验证的东西。

今天我在Delphi Pascal中实现了一个设计(因为Delphi是我首先需要的)。像这个API那样调度参数类型并不是一个原创的想法(它解释了设计模式 Visitor模式),我想我以前见过这样的东西(但是我不记得在哪里; Taligent?)。它的核心是订阅,过滤和调度都在类型系统上。

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实现的完整源代码是here

这是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. 然而,在另一个个人数据应用程序中,我需要抽象事件传递和处理,因为持久性和UI会因平台而异,必须完全独立。
  4. 参考

    由self和其他人发现应该到这里

    • PybubSub使用字符串表示topycs和方法签名(第一个信号定义签名)。
    • An article博客中的
    • FinalBuilder报告说他们已成功使用整数结构的系统作为有效负载,消息和整数掩码进行过滤。
    • PyDispatcher的文档很少。
    • D-Bus已被Gnome和KDE项目采用。 Python binding可用。

2 个答案:

答案 0 :(得分:2)

你应该尝试Enterprise Integration Patterns它为发布 - 订阅提供了非常详细的处理,尽管它集中在进程间消息传递上。

答案 1 :(得分:2)

你也可以尝试DDS。数据分发服务是使用发布/订阅语义的通信模式的完整标准。