许多魔术数字

时间:2016-10-17 15:08:22

标签: c# linq magic-numbers

我有一个项目,我将消息从一种格式转换为另一种格式。 消息由id标识。我有一个带有一种格式的id的MappingTable和另一种格式的相应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扩展了Baseclass,所有其他类扩展了BaseRecord。总共我有5个Record类型,其中recordId为1-5。 在这条记录中有几个subrecordId(~50),它们仅用于在写入过程后分别识别作者背后的记录。

问题在于某些记录从不同的字段中读取了一些值,而其他记录则导致我需要识别记录的一些特殊情况。

这会导致问题: 如果我在课堂和转换器中使用它们,我有许多神奇的数字,没有人知道它们的用途。我如何避免这些神奇的数字?我很开心,如果我使用那些,我的课程中会有50多个const值。有没有办法避免魔术数字和一大堆const值?对此有什么正确的重构?

7 个答案:

答案 0 :(得分:5)

如果您可以将特殊行为概括为一组操作,那么您应该可以这样做:

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);
}

答案 1 :(得分:3)

这是Factory design pattern可以派上用场的地方。

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

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-case但我认为你应该使用Dependency Injection

在以下实现中,您将传递所有非特殊情况的映射和默认行为。我认为这是一种优雅的方式,只能映射您需要的内容并保持其有机化

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;}
}

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

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

无论您选择什么,在完成上述结构后,您的服务代码应该看起来像:

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

评论后更新:

  

但是在转换过程中我将不得不在其他一些地方使用魔法数字,因为我在很多不同的地方都有魔法数字

您可以做的是在IMessageHandler Code中定义另一个属性 并有一个抽象的基类,强制必须设置Code。通过这样做,您实际上说“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)。

在这里您描述了不同的服务。像这样的每个服务仍然可以依赖于上面描述的相同类型的工厂,但是特定工厂的初始化以及其中每个Code的{​​{1}}的初始化可能不同。

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

答案 2 :(得分:1)

一般来说,这是一个最好通过修复它来解决的问题。如果这不是一个选项,我会使用switch语句来处理特殊行为。如果可以的话,尽可能少地在switch-cases中尽可能少地在一般处理程序中执行。

答案 3 :(得分:0)

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

答案 4 :(得分:0)

为什么不向正在使用的Message类添加一个扩展方法,该方法包含一个告诉你&#34; type&#34;的属性。如果&#34;键入&#34;众所周知,在做出决定时,请考虑使用Enum进行比较。但是,您可以在构造消息时分配枚举(例如),这样每次检查消息类型时都不会检查id。但是,如果类型是一个长列表,只需使用一个字符串来表示类型(在代码中读取时更有意义),如果可能的话,再次将代码转换为在构造中的一个位置的字符串。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

我会写一个可能会得到许多下注的矫枉过正。

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

使用该字符串,我会添加前缀“Special”,它看起来像Special_153

此名称为class

然后我会创建一个包含所有特殊类

的文件夹

Special_153.cs Special_154.cs

每个Special类都会继承自ISpecial,它只实现方法Run

方法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();

在此扩展程序中,您将检查邮件是否是特殊邮件。这样您就可以在其他地方使用此代码。