【问题标题】:Choosing the most specific handler when multiple handlers would do当多个处理程序都可以时选择最具体的处理程序
【发布时间】:2013-07-05 04:06:16
【问题描述】:

对不起标题。

我有一个系统是面向服务架构的一部分。它接收消息并处理它们。该过程可以简单地归结为将数据从一个位置移动到另一个位置。系统做出的所有决定都可以通过检查系统始终可用的两个不同类来做出:

  1. 正在处理的消息
  2. 特定数据操作的配置信息(从哪里移动到哪里等)

这里是主要界面

    public interface IComponent
    {
        bool CanHandle(Message theMessage, Configuration theConfiguration);
        int Priority {get;}
    }

    public interface IComponentLocator<T>
        where T : IComponent
    {
        public LocateComponent(Message theMessage, Configuration theConfiguration);
    }

我使用 Castle Windsor 框架解决依赖倒置问题,因此我实现的一个定位器接收通过数组解析器注入的所有适当组件。

这里是:

public class InjectedComponentsLocator<T> : IComponentLocator<T>
    where T : IComponent
{
    private readonly T[] components;

    public InjectedComponentsLocator(T[] components)
    {
        this.components = components;
        this.components.OrderBy((component) => component.Priority);
    }

    public T LocateComponent(Message theMessage, Configuration theConfiguration)
    {
        List<T> candidates = this.components.Where((h) => h.CanHandle(message, configuration)).ToList();

        if (candidates.Count == 0)
        {
            throw new Exception(string.Format(Resources.UnableToLocateComponentException, typeof(T).Name));
        }
        else if (candidates.Count > 1 && candidates[0].Priority == candidates[1].Priority)
        {
            throw new Exception(string.Format(Resources.AmbiguousComponentException, candidates[0].GetType().Name, candidates[1].GetType().Name));
        }

        return candidates.First();
    }
}

现在问题。 IComponent 接口上的Priority 属性.. 我不喜欢它。实际情况是优先级应该可以通过最具体的IComponent来确定。

例如,假设我有两个组件。

    public class HandlesOneRecord : IComponent
    {
        public bool CanHandle(Message theMessage, Configuration theConfiguration)
        {
            return theMessage.BatchSize == 1;
        }
    }

    public class HandlesOneInsert : IComponent
    {
        public bool CanHandle(Message theMessage, Configuration theConfiguration)
        {
            return theMessage.BatchSize == 1 && theMessage.Action = "Insert";
        }
    }

我希望系统知道一条记录的插入消息需要选择第二条,因为它是最具体的一条。现在我需要设置不同的优先级,这感觉在创建新组件时会变得笨拙并产生错误。

添加以尝试澄清
如果系统最终以我设想的方式工作,我将能够拥有两个组件,一个将处理任何“插入”类型的操作,还有一个特定组件将处理批量大小 = 1 的“插入”。任何开发人员写作代码不必关心系统是否选择了正确的,它只会选择正确的。

谢谢!

【问题讨论】:

  • 出于好奇,如果您只希望一个组件来处理给定的消息,为什么有多个组件能够处理它?
  • 不确定这是否适合你,但也许看看责任链设计模式。一个组件将是起始组件,如果它不能处理该消息,它将把它传递给下一个组件,依此类推。
  • 为了找到问题的根源,我过于简单化了,这可能就是为什么感觉做作的原因。至于责任链,我考虑过了。我想不出一种方法,我在链中没有同样的问题,也就是说,确定哪一个是最具体的。
  • app.config 或数据库中的键/值对映射怎么样?关键是组件关心的消息部分。该值将是最负责处理它的类型/组件。然后你可以有一个工厂方法,它使用反射来根据键找到并激活正确的组件。

标签: c# design-patterns castle-windsor soa


【解决方案1】:

您可以通过使用评分系统来做到这一点。

public sealed class Score
{
    private int _score;

    public Score(params bool[] conditions)
    {
        foreach(bool b in conditions)
        {
           if(b)
              ++_score;
           else
           {
              _score = 0;
              break;
           }
        }
    } // eo ctor

    public int Total { get { return _score; } }
}  // eo class Score

您的IComponent 可能如下所示:

public interface IComponent
{
    Score CanHandle(Message theMessage, Configuration theConfiguration);
}  // eo IComponent

专业:

public class HandlesOneRecord : IComponent
{
    public Score CanHandle(Message theMessage, Configuration theConfiguration)
    {
        return new Score(theMessage.BatchSize == 1);
    }
}

public class HandlesOneInsert : IComponent
{
    public Score CanHandle(Message theMessage, Configuration theConfiguration)
    {
        return new Score(theMessage.BatchSize == 1, theMessage.Action == "Insert");
    }
}

然后找到最合适的处理程序,遍历并选择得分最高的处理程序。如果您有 100 多个处理程序,则可以通过为 Message 生成哈希码并将成功的查找存储在 Dictionary&lt;int, Func&lt;Message, Configuration, Score&gt;&gt; 中来提高其性能。

【讨论】:

  • 我喜欢这个,但是,并不是最“真实”的组件获胜。它更多的是关于评估为真的函数。如果一个组件中所有评估为真的函数与另一个组件中评估为真的函数完全相同,并且第二个组件有任意数量的其他函数也评估为真,我需要选择第二个函数。这有意义吗?
  • 那么为什么不使用它作为你的算法呢?在每个组件上都有一个外观/工厂调用 GetScore()。然后让工厂返回得分最高的组件。
  • @greyalien007,在这种情况下,第二个组件的分数会更高,不是吗?
  • 你说得对,我现在明白了。之所以选择它们,是因为它们被确定为可能的处理程序,因此归结为最终有多少函数结果是正确的。感谢您的帮助!
【解决方案2】:

鉴于您的域已经相当面向消息,您是否考虑过使用某种消息总线,例如NServiceBus

这些类型的基础架构具有您似乎正在寻找的消息处理管道,包括诸如基于类型分派到处理程序,甚至具有多态性以及定义处理程序顺序和每个处理程序的能力能够停止管道。

【讨论】:

  • 我的 SoA 目前正在 RhinoBus 上运行。我有一个消费者/调度员模式,正如你所建议的那样。我所说的所有工作都是在消费操作收到适合当前端点的消息之后进行的。消费然后为消息调用适当的处理器。在处理器内部,我完成了这篇文章所讨论的所有逻辑。现在也许你说的是我应该让我的框架为我做更多的决定(或者你可能不是:))。我会检查一下,谢谢你的回复。
  • 我没有意识到 RhinoBus 没有这些能力 - 所以,嗯,是的。
  • 可以的。我为总线技术创建了我们自己的接口,并为 rhino 总线创建了一个适配器,这样如果它不够好我可以在以后扔掉 rhino。我会多花点时间。
【解决方案3】:

您似乎想根据消息中包含的数据而不是其类型来选择组件。

Specification pattern 是一个不错的可扩展选项。您可以拥有 Bob Horn 建议的责任链,其中每个组件将根据注入的规范确定它是否可以处理消息。这些方面的东西:

public abstract class Component 
{
  protected Component _next;
  protected MessageSpecification _specification;

  public Component(MessageSpecification specification)
  {
    // Inject specification here
  }

  public void Inspect(Message message)
  {
    if (_specification.IsSatisfiedBy(message))
    {
       Handle(message);
    }
    else _next.Inspect(message);
  }

  abstract void Handle(Message message);
}

在系统的其他部分,您需要:

  • 定义每个具体组件和相关规范(如果有)。

  • 按特定顺序组装责任链。这可以基于域规则(您可能是唯一知道的)来完成,或者,假设您的所有规范都是互斥的,将所有基于规范的组件放在链中的首位。

【讨论】:

  • 一个不错的想法,但在 OP 示例中,HandlesOneRecord 可以在这两种情况下使用,但HandlesOneInsert 是一个更专业的版本,他或她会想要那个版本。
  • 使用以下规格可以工作:message =&gt; message.BatchSize == 1message =&gt; message.BatchSize == 1 &amp;&amp; message.Action = "Insert" 不是吗?
  • 诚然,我可能误解了您的答案,但HandlesOneRecord 在检查消息批量大小时会感到满意,HandlesOneInsert 永远不会看到?
  • 如果链条以正确的顺序组装,则不是最严格的组件。为您的解决方案 +1,因为这正是定义该顺序的好方法;)
猜你喜欢
  • 1970-01-01
  • 2018-05-03
  • 1970-01-01
  • 2021-12-28
  • 2018-08-09
  • 2017-08-05
  • 2011-07-05
  • 2013-09-04
  • 1970-01-01
相关资源
最近更新 更多