我有一个项目,我将消息从一种格式转换为另一种格式。 消息由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值?对此有什么正确的重构?
答案 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();
在此扩展程序中,您将检查邮件是否是特殊邮件。这样您就可以在其他地方使用此代码。