1

我正在编写一个事件管理器,它将采用许多不同的事件处理程序。该事件管理器将收到许多不同事件的通知。每个处理程序只处理某些事件,而忽略其余的。每个处理程序还可以根据情况触发某些其他事件。

例如,第一个处理程序来处理Event1

-module (first_handler).
-behavior (gen_event).

...

handle_event(Event1, State) -> {ok, State};
handle_event(_, State) -> {ok, State}.

第二个处理程序来处理Event2

-module (second_handler).
-behavior (gen_event).

...

handle_event(Event2, State) -> 
  gen_event:notify(self(), Event1),
  {ok, State};
handle_event(_, State) -> {ok, State}.

事件触发可以通过在处理程序中调用来完成gen_event:notify(self(), NewEvent)handle_event但我宁愿将其抽象出来并将其导出,以便可以从事件管理器中调用它。

由于模式匹配和忽略事件以及触发事件对所有处理程序都是通用的,所以无论如何我可以扩展gen_event行为以将它们作为内置提供吗?

我将从创建自定义行为的默认方式开始:

-module (gen_new_event).
-behaviour (gen_event).

behaviour_info(Type) -> gen_event:behaviour_info(Type).

我不确定下一步该怎么做。

4

2 回答 2

3

你到底想做什么?从您提供的示例中我无法理解。在 second_handler 中handle_event/2Event1未绑定。另外,使用self()有效吗?那不应该是经理的注册名。不确定是handle_event/2由管理器还是每个处理程序进程执行(但后者更有意义)。

通过实现您的gen_new_event模块,您正在实现一个处理程序(即回调模块),而不是一个事件管理器。您拥有的事实-behaviour(gen_event)意味着您要求编译器检查是否gen_new_event实际实现了 列出的所有函数gen_event:behaviour_info(callbacks),从而制作gen_new_event了一个合格的处理程序,您可以通过它添加到事件管理器中gen_event:add_handler(manager_registered_name, gen_new_event, [])

现在,如果你带走-behaviour (gen_event)gen_new_event不再需要实现以下功能:

35> gen_event:behaviour_info(callbacks).   
[{init,1},
 {handle_event,2},
 {handle_call,2},
 {handle_info,2},
 {terminate,2},
 {code_change,3}]

您可以gen_new_event通过添加更多功能来实现行为(即接口),您将需要任何-behaviour(gen_new_event)用于实现的模块:

-module (gen_new_event).
-export([behaviour_info/1]).

behaviour_info(callbacks) -> 
    [{some_fun, 2}, {some_other_fun, 3} | gen_event:behaviour_info(callbacks)].

现在,如果在某个模块中,例如-module(example),您添加属性-behaviour(gen_new_event),那么该模块example将必须实现所有gen_event回调函数 +some_fun/2some_other_fun/3

我怀疑这就是您要寻找的东西,但是您的最后一个示例似乎表明您想要实现一种行为。请注意,您通过实现一个行为所做的只是要求其他模块在它们使用时实现某些功能-behaviour(your_behaviour)

(另外,如果我理解正确,如果你想扩展,gen_event那么你总是可以简单地复制代码gen_event.erl并扩展它......我猜,但这对于你想要做的事情真的有必要吗?)。

编辑

目标:从 gen_event 实现中提取通用代码。因此,例如,您希望在每个 gen_events 中都有一个 handle_event/2 子句。

一种解决方法:您可以使用参数化模块。该模块将实现 gen_event 行为,但仅实现所有 gen_event 回调模块应具有的常见行为。任何不“常见”的东西都可以委托给模块的参数(您可以将其绑定到包含 gen_event 回调的“自定义”实现的模块名称。

例如

-module(abstract_gen_event, [SpecificGenEvent]).
-behaviour(gen_event).
-export(... all gen_event functions).

....

handle_event({info, Info}, State) ->
    %% Do something which you want all your gen_events to do.
handle_event(Event, State) ->
    %% Ok, now let the particular gen_event take over:
    SpecificGenEvent:handle_event(Event, State).

%% Same sort of thing for other callback functions
....

然后,您将实现一个或多个 gen_event 模块,您将插入到 abstract_gen_event 中。可以说其中之一是a_gen_event。

然后你应该能够做到:

AGenEvent = abstract_gen_event:new(a_gen_event). %% Note: the function new/x is auto-generated and will have arity according to how many parameters a parameterized module has.

然后,我想您可以将 AGenEvent 传递给 gen_event:add_handler(some_ref, AGenEvent, []) 它应该可以工作,但请注意我从未尝试过。

也许您也可以使用宏来解决这个问题,或者(但这有点矫枉过正)在编译时使用 parse_transform/2 进行一些操作。不过只是一个想法。看看这个参数化的解决方案是如何先行的。

第二次编辑

(注意:不确定我是否应该删除本节之前的所有内容。请告诉我,或者如果您知道自己在做什么,请直接删除)。

好的,所以我自己尝试了一下,是的,参数化模块的返回值在将其提供给 gen_event:add_handler/3 的第二个参数时会崩溃......太糟糕了:(

除了 a) 使用宏 b) 使用 parse_transform/2 之外,我想不出任何其他方法来解决这个问题。

一种)

-module(ge).
-behaviour(gen_event).

-define(handle_event, 
handle_event({info, Info}, State) ->
    io:format("Info: ~p~n", [Info]),
    {ok, State}).

?handle_event;
handle_event(Event, State) ->
    io:format("got event: ~p~n", [Event]),
    {ok, State}.

因此,基本上,您将在头文件中包含宏定义中定义的通用功能的所有回调函数子句,该头文件包含在使用此通用功能的每个 gen_event 中。然后你?X 在每个使用通用功能的回调函数之前/之后......我知道它不是那么干净,我通常厌倦自己使用宏但是嘿......如果问题真的让你烦恼,那是一种方法去做吧。

b) 谷歌搜索有关在 Erlang 中使用 parse_transform/2 的一些信息。您可以实现一个 parse_transform,它在您的 gen_event 模块中查找回调函数,这些模块具有回调的特定情况,但没有通用情况(即上述宏中的 ({info, Info}, State) 之类的子句)。然后,您只需添加构成通用案例的表格。

我建议做这样的事情(添加导出):

-module(tmp).
 parse_transform(Forms, Options) ->
     io:format("~p~n", [Forms]),
     Forms.

-module(generic).
gen(Event, State) ->
    io:format("Event is: ~p~n", [Event]),
    {ok, State}.

现在你可以编译:

c(tmp).
c(generic, {parse_transform, tmp}).
[{attribute,1,file,{"../src/generic.erl",1}},
 {attribute,4,module,generic},
 {attribute,14,compile,export_all},
 {function,19,gen,2,
       [{clause,19,
                [{var,19,'Event'},{var,19,'State'}],
                [],
                [{call,20,
                       {remote,20,{atom,20,io},{atom,20,format}},
                       [{string,20,"Event is: ~p~n"},
                        {cons,20,{var,20,'Event'},{nil,20}}]},
                 {tuple,21,[{atom,21,ok},{var,21,'State'}]}]}]},
 {eof,28}]
 {ok,generic}

这样您就可以复制粘贴您将要注入的表单。您可以将它们复制到适当的 parse_transform/2 中,而不仅仅是打印,它实际上会遍历您的源代码并将您想要的代码注入到您想要的位置。

作为旁注,您可以将属性包含-compile({parse_transform, tmp})到您的每个 gen_event 模块中,这些模块需要以这种方式进行 parse_transformed 以添加通用功能(即,避免自己将其传递给编译器)。只需确保tmp在路径上的目录中加载或编译包含您的 parse_transform 的任何模块。

b) 我知道的工作似乎很多......

于 2012-06-29T22:23:03.723 回答
2

您安装的处理程序已经在您启动然后安装处理程序的事件管理器的上下文中运行。因此,如果他们的句柄事件函数抛出数据,他们已经做了你想要的。

您不需要扩展事件行为。你要做的是:

 handle_event(Event, State) ->
   generic:handle_event(Event, State).

然后让generic模块处理通用部分。请注意,如果需要,您可以提供generic一种方法来回调此处理程序模块以实现专门的处理程序行为。例如:

   generic:handle_event(fun ?MODULE:callback/2, Event, State)...

等等。

于 2012-06-29T12:12:42.593 回答