许多神奇的数字
本文关键字:数字 神奇 许多 | 更新日期: 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);
}
评论后更新:
但在转换过程中,我将不得不在其他地方使用幻数,这很糟糕,因为我在许多不同的地方都有幻数
您可以在Code
的IMessageHandler
中定义另一个属性并且具有强制必须设置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(。
在这里您介绍不同的服务。像这样的每个服务仍然可以依赖于上面描述的相同类型的工厂,但是该特定工厂和其中每个Handler
的Code
s的初始化可以不同。
如果我使用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.cs
Special_154.cs
每个Special
类都将继承自仅实现方法Run
的ISpecial
。
方法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();
在这个扩展插件中,您可以检查消息是否是一个特殊的消息。通过这种方式,您可以在其他地方使用此代码。