Feuilles de root

Logiciels libres, programmation et économie

Accueil » Programmation » Programmation Free Pascal » Implémentation d'un bus de messages

Implémentation d'un bus de messages

Lorsqu'on développe un jeu ou une application d'entreprise, l'une des principale préoccupation est d'organiser le code en découplant les composants pour éviter un code spaghetti difficile à maintenir.

Qu’est-ce qu’un bus de messages ?

Les applications et les jeux sont normalement composées de nombreux composants. Chaque composant est responsable d’une tâche ou d’une fonctionnalité de l’application. Un composant doit parfois communiquer avec un autre composant de l’application.

Un bus de message est simplement une version modifiée et utilisée à grande échelle du patron de conception Observateur. Dans le patron de conception Observateur, des Observateurs reçoivent des notifications envoyées par des Sujets. Le rôle d’un bus de messages est de router ces messages à tous les composants qui y sont connectés. Les messages envoyés par les composants correspondent simplement à des événements qui se produisent.

Implémenter le bus de messages

Commençons par implémenter l’interface IMessage. Chaque type de message sera implémenté sous forme d’une classe implémentant l’interface IMessage. Pourquoi ne pas simplement utiliser une constante ou une énumération ? L'utilisation d'une classe permet à l’objet message de véhiculer des informations supplémentaires.

{ IMessage }

IMessage = interface
    ['{B23321E7-FC20-43F6-8D69-4E096F6EAD5A}']
    function GetType : String;
end;

Les classes représentants les messages implémentent l'interface IMessage :

{ TKeyPressed }

TKeyPressed = class(TInterfacedObject, IMessage)
private
    FKey : Char;
public
    constructor Create(const AKey : Char);
    function GetKey : Char;
    function GetType : String;
end;
{ TKeyPressed }

constructor TKeyPressed.Create(const AKey : Char);
begin
    FKey := AKey;
end;

function TKeyPressed.GetKey : Char;
begin
    Result := FKey;
end;

function TKeyPressed.GetType : String;
begin
    Result := 'keypressed';
end;

Les classes susceptibles de recevoir des messages et de les traiter doivent implémenter l’interface IMessageReceiver. Celle-ci définit une seule méthode :

{$Interfaces CORBA}

{ IMessageReceiver }

IMessageReceiver = interface
    procedure HandleMessage(AMessage : IMessage);
end;

Notez ici la directive de compilation permettant d'utiliser des interfaces CORBA au lieu des interfaces COM, afin d'éviter d'obliger à étendre TInterfacedObject.

A présent nous allons implémenter le bus de messages lui-même.

J’utilise ici les collections génériques TGlist et TDictionary.

TMessageReceiverList = specialize TGlist<IMessageReceiver>;
TSubscriberDictionary = specialize TDictionary<String, TMessageReceiverList>;
TMessageList = specialize TGlist<IMessage>;

FMessageQueue est une file d’attente.

La méthode PostMessage permet de poster un message qui sera placé dans la file d’attente et diffusé de manière différée lors de l’appel à la méthode Notify.

{ TMessageBus }

TMessageBus = class
protected
    FSubscriberLists : TSubscriberDictionary;
    FMessageQueue : TMessageList;
public
    constructor Create;
    procedure Subscribe(const AMessageType : String; AMessageReceiver : IMessageReceiver);
    procedure Unsubscribe(const AMessageType : String; AMessageReceiver : IMessageReceiver);
    procedure PostImmediateMessage(AMessage : IMessage);
    procedure PostMessage(AMessage : IMessage);
    procedure Notify;
    destructor Destroy; override;
end;

La méthode Subscribe permet à un composant implémentant IMessageReceiver de s’abonner à un type de message.

La méthode Unsubscribe permet à un composant de se désabonner.

procedure TMessageBus.Subscribe(const AMessageType : String;
    AMessageReceiver : IMessageReceiver);
var
    LSubscribers : TMessageReceiverList;
begin
    if not FSubscriberLists.ContainsKey(AMessageType) then
        FSubscriberLists.Add(AMessageType, TMessageReceiverList.Create);
    LSubscribers := FSubscriberLists[AMessageType];
    if not LSubscribers.Contains(AMessageReceiver) then
        LSubscribers.Add(AMessageReceiver);
end;

La méthode PostImmediate­Message permet de poster un message qui sera diffusé immédiatement à tous les objets abonnés à ce type de message.

procedure TMessageBus.PostImmediateMessage(AMessage : IMessage);
var
    LSubscribers : TMessageReceiverList;
    i : Integer;
begin
    if FSubscriberLists.ContainsKey(AMessage.GetType) then
    begin
        LSubscribers := FSubscriberLists[AMessage.GetType];
        for i := 0 to LSubscribers.Count - 1 do
            LSubscribers[i].HandleMessage(AMessage);
    end;
end;
procedure TMessageBus.Notify;
begin
    with FMessageQueue do begin
        while Count > 0 do begin
            PostImmediateMessage(First);
            Delete(0);
        end;
    end;
end;

Code source