実際に機能する軽量のパブリッシュ/サブスクライブ メカニズムの設計と実装について、Wikipedia スタイルのリファレンスを探しています。回答とコメント、および私自身の調査に従って、質問を更新します。
私は自分の本や 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 実装の完全なソース コードは次のとおりです。
これは Python での実装です。シグナルとメッセージはすでにオーバーロードされているため、キー名を変更しました。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())
これらは私の検索の理由です:
- 私は維持しなければならない数千行の Delphi コードを調べてきました。彼らはMVC デカップリングにオブザーバーパターンを使用していますが、オブザーバーとサブジェクトの間の依存関係が明示的すぎるため、すべてが非常に結合されています。
- 私は PyQt4 を学んでいますが、意味のある目的地に到着したいすべてのイベントに対して Qt4Designer でクリック-クリック-クリックする必要がある場合、それは私を殺します。
- さらに、別の個人データ アプリケーションでは、イベントの受け渡しと処理を抽象化する必要があります。これは、持続性と UI がプラットフォームによって異なり、完全に独立している必要があるためです。
参考文献
自分で見つけて、他の人はここに行くべきです
- PybubSubは topycs とメソッド シグネチャに文字列を使用します (最初のシグナルがシグネチャを定義します)。
- FinalBuilderのブログの記事では、ペイロード、メッセージ、およびフィルタリング用の整数マスクとして整数構造を持つシステムを使用して成功したと報告しています。
- PyDispatcherのドキュメントは最小限です。
- D-Busは Gnome や KDE プロジェクトなどで採用されています。Python バインディングが利用可能です。