许多神奇的数字

本文关键字:数字 神奇 许多 | 更新日期: 2023-09-27 18:13:20

我有一个项目,可以将消息从一种格式转换为另一种格式。消息由id标识。我有一个MappingTable,id采用一种格式,对应的id采用另一种格式。

例如:2000=153

id是整数

我通过添加所有这些条目

this.AddMappingEntry(2400, 2005, true, false);

此方法将映射条目添加到映射列表中。我可以通过linq过滤列表,这样,如果我收到值为2400的消息,我就可以找到正确的映射。

自从项目启动以来,它已经发展了很多,这导致了许多重构和许多具有特殊行为的id。我必须控制消息是否属于特殊类型。

if(message.id == 153)
{
    //special behavior
}

我该如何处理这个最优雅的问题?我应该使用许多常量来描述消息类型,还是有其他更优雅的方式?

编辑:

我改写了这个问题。我有主记录和子记录。但这些记录的Id在整个代码中都被使用。由于大约有200个不同的Id,我想知道该如何处理这些神奇的数字。我正在写的工具是一个转换器。

结构如下

     +------+         +-------------+      +---------+
     | DAL  +-------> |  CONVERTER  +----> | WRITER  |
     +------+         +-------------+      +---------+

Converter类大致类似于这个

    +------------+
    |BaseClass   |
    +-----+------+
          ^
          |
    +-----+------+
    |BaseRecord  +^-------------+----------------------+
    +------+-----+              |                      |
           ^                    |                      |
           |                    |                      |
    +------+-----+      +-------+--------+     +-------+--------+
    | HeadRecord |      |   RecordType1  |     |   RecordType2  |
    +------------+      +----------------+     +----------------+

BaseRecord扩展基类,所有其他类扩展BaseRecord。我总共有5种记录类型,记录ID为1-5。在该记录中有几个子记录ID(~50(,它们仅用于在写入过程之后分别识别写入器后面的记录。

问题是,有些记录从不同的字段中读取一些值,而另一些则读取,这导致了一些特殊情况,我需要识别记录。

这导致了问题:我有很多神奇的数字,如果我在我的类和转换器中使用它们,没有人知道它们是用来做什么的。如何避免那些神奇的数字?我担心,如果我使用类,它们会被50多个常量值填充。有没有办法避免幻数和一大堆常量值?对此,正确的重构是什么?

许多神奇的数字

如果您可以将特殊行为概括为操作集合,那么您应该能够这样做:

private static readonly IDictionary<int,Action> messageBehavior =
    new Dictionary<int,Action> {
        {153, () => { Console.WriteLine("Special action"); } },
        {154, () => { Console.WriteLine("Another special action"); } }
    };

现在,您可以通过消息ID从字典中获取操作,并在可用时运行它:

Action special;
if (messageBehavior.TryGetValue(message.id, out special)) {
    special();
}

如果操作需要一些特殊的上下文来运行,例如触发操作的消息,您可以使用Action<Message>,并将消息传递给它:

private static readonly IDictionary<int,Action<MyMessageType>> messageBehavior =
    new Dictionary<int,Action> {
        {153, (m) => { Console.WriteLine("Special action; message {0}", m); } },
        {154, (m) => { Console.WriteLine("Another special action ({0})", m); } }
    };
...
Action<MyMessageType> special;
if (messageBehavior.TryGetValue(message.id, out special)) {
    special(message);
}

这是工厂设计模式派上用场的地方。

为一个类定义一个接口,该类将知道如何处理单个特殊行为:

public interface IMessageHandler
{
    IMessage Transform(IMessage message);
}

然后你会有不同的实现。例如:

public class DefaultMessageHandler
{
    IMessage Transform(IMessage message)
    {
        return message;
    }
}
public class BehaviorXMessageHandler
{
    IMessage Transform(IMessage message)
    {
        message.SomeProperty = "hello world";
        return message;
    }
}

然后你就有了你的工厂:

public interface IMessageHandlerFactory
{
    IMessageHandler GetHandler(int messageCode);
}

工厂的一个可能的实现是使用switch案例,但我认为你应该使用依赖注入:

在下面的实现中,您为所有非特殊情况传递映射和默认行为。我认为这是一种优雅的方式,只映射你需要的东西,并保持其组织化

public class MappingMessageHandlerFactory : IMessageHandlerFactory
{
    public MappingMessageHandlerFactory(Dictionary<int,IMessageHandler> mapping, IMessageHandler defaultBehavior)
    {
        Mapping = mapping;
        DefaultBehavior = defaultBehavior;
    }
    public IMessageHandler GetHandler(int messageCode)
    {
        IMessageHandler output = DefaultBehavior;
        Mapping.TryGetValue(messageCode, out output);
        return output;
    }
    public Dictionary<int,IMessageHandler> Mapping {get; set;}
    public IMessageHandler DefaultBehavior {get;set;}
}

为了让工厂接收映射,你可以自己初始化它,或者使用许多IoC容器中的一个,如Castle Windsor、Ninject、Unity等。

其中一些容器甚至为您提供了工厂的通用实现,您只需要提供接口。在温莎城堡,它被称为TypedFactoryFacility

无论你选择什么,在浏览了上面的结构后,你的服务代码应该看起来像:

public IMessage ProcessMessage(IMessage message)
{
    var handler = _messageHandlerFactory.GetHandler(message.Code);
    return handler.Transform(message);
}

评论后更新:

但在转换过程中,我将不得不在其他地方使用幻数,这很糟糕,因为我在许多不同的地方都有幻数

您可以在CodeIMessageHandler中定义另一个属性并且具有强制必须设置CCD_ 5的抽象基类。这样做实际上意味着"MessageHandler是知道它负责哪条消息的人"。

public interface IMessageHandler
{
    IMessage Transform(IMessage message);
    int Code {get; set;}
}
public abstract class MessageHandlerBase : IMessageHandler
{
    public MessageHandlerBase(int code) 
    {
        Code = code;
    }
    public abstract IMessage Transform(IMessage message);
    public int Code {get; set;}
}

它将不再收到字典,而是收到IEnumerable<IMessageHandler>,这样就不会在某个地方再次定义数字了。(您可以实现它将IEnumerable转换为字典,因此搜索仍将在o(n)中,但这是实现细节

例如,消息2002(更改用户(是发送系统中的一条消息。接收系统没有改变用户,而只有登录(106(和注销(107(。

在这里您介绍不同的服务。像这样的每个服务仍然可以依赖于上面描述的相同类型的工厂,但是该特定工厂和其中每个HandlerCodes的初始化可以不同。

如果我使用Castle Windsor作为示例,它可以选择通过xml配置依赖项。因此,每个服务都可以有一个这样的部分,在它的app.config中,您可以创建实际想要的行为以及Code映射到它们的映射

通常,这是一个最好通过修复它来解决的问题。如果这不是一个选项,我会使用switch语句来处理特殊行为。如果可以的话,在切换情况下尽可能少地执行操作,在通用处理程序中尽可能多地执行操作。

我会在App.Config上设置它,并使用Configuration Manager来获取数据。

为什么不在您正在使用的Message类中添加一个扩展方法,该方法包含一个告诉您"类型"的属性。如果"类型"是预先已知的,则在做出决策时考虑使用枚举进行比较。然而,您可以在消息构造时分配枚举(例如(,这样您就不会在每次检查消息类型时都检查id。然而,如果类型是一个长列表,只需使用字符串来表示类型(在代码中读取时更有意义(,如果可能的话,在构造时再次将代码转换为一个位置的字符串。

我会有一组SpecialMessageHandler类和一个StandardMessageHandler类,然后进行某种查找(比如Dictionary(。不用if/switches,而是在字典中查找消息id,如果存在,则使用专用类,如果不使用标准类。您甚至可以在代码之外指定Dictionary的内容(例如web/app.config(。

我会写一篇夸张的文章,可能会得到很多反对票。

我会创建一个带有特殊代码(SpecialIds(的枚举。然后,当检查这个数字是否是一个特殊的数字时,我会将Id转换为字符串。

有了这个字符串,我会添加前缀"Special",看起来像Special_153

此名称将是class

然后我会创建一个文件夹,里面有所有的特殊类

Special_153.csSpecial_154.cs

每个Special类都将继承自仅实现方法RunISpecial

方法Run将在构造函数内部调用。构造函数将接收完整的消息对象。

然后我会这样使用它:

if (Enum.GetNames(typeof(SpecialIds)).Contains(message.id))
{
    //special behavior
    string id = message.Id.ToString();
    string prefix = "Special_";
    string className = prefix + id;
    Type t = Type.GetType(className);
    ConstructorInfo constructor = t.GetConstructor(new Type[] { message.GetType() });
    constructor.Invoke(message);
}

constructor.Invoke(message);将使用参数message调用构造函数。在类方法Run中,您可以修改对象message中所需的所有内容。

这样,每个特殊Id就有一个类,也可以称之为handlers。它们将在该文件夹中,如果您想编辑200个处理程序中的1个,您可以很容易地访问代码。

编辑1

哦,最后,你可以写一个扩展,单独执行constructor调用,只需调用:

message.CheckSpecial();

在这个扩展插件中,您可以检查消息是否是一个特殊的消息。通过这种方式,您可以在其他地方使用此代码。