我有一个库,它有两种输入格式供库描述的对象模型使用。我目前使用事件订阅模型向库的最终用户发出错误/警告/详细消息。这并没有被证明是最干净的模型,我想知道在.Net框架(在罗马时)是否有相关的设计模式或类似的东西来更好地处理这种情况。
// Rough outline of the current code
public abstract class ModelReader : IDisposable
{
public abstract Model Read();
public event EventHandler<MessageAvailableEventArgs> MessageAvailable;
protected virtual void RaiseError(string message)
{
var handler = this.MessageAvailable;
if (handler != null)
{
handler(this, new MessageAvailaibleEventArgs(
TraceEventType.Error, message);
}
}
}
修改:一些澄清。 Read
例程在使用异常的所有致命错误上已经快速失败。消息被记录到用户端的潜在多个源,因此任何模式都应该避免限制潜在来源的数量。
答案 0 :(得分:3)
我可以举一个现实世界的例子。 Html Agility Pack库是一个解析库。它只是在reader类上定义了一个解析错误列表。扩展您的示例,它将类似于:
public abstract class ModelReader
{
private List<ParseError> _errors = new List<ParseError>();
private bool _throwOnError;
public ModelReader()
:this(true)
{
}
public ModelReader(bool throwOnError)
{
_throwOnError = throwOnError;
}
// use AddError in implementation when an error is detected
public abstract Model Read();
public virtual IEnumerable<ParseError> Errors
{
get {return _errors;}
}
protected virtual void AddError(ParseError error)
{
if (_throwOnError) // fail fast?
throw new ParseException(error);
_errors.Add(error);
}
}
public class ParseError
{
public ParseError(...)
{
}
public ParseErrorCode Code { get; private set; }
public int Line { get; private set; }
public int LinePosition { get; private set; }
public string Reason { get; private set; }
public string SourceText { get; private set; }
public int StreamPosition { get; private set; }
}
public enum ParseErrorCode
{
InvalidSyntax,
ClosingQuoteNotFound,
... whatever...
}
public class ParseException: Exception
{
...
}
如果图书馆来电者想要即时活动,您仍然可以添加活动。
答案 1 :(得分:2)
您似乎想要一个可组合的验证器,您的用户可以在其中插入自己的逻辑,将特定的非致命错误标记为致命错误,警告或信息性消息。如果另一个部分想要将其转换为警告但希望继续解析,则抛出异常并不适合该法案,因为您已经离开了该方法。这听起来很像Windows中的结构化异常处理中的两遍异常处理模型。 它基本上就像
这两个传递模型的优点在于,在第一次传递过程中会询问所有处理程序,但您仍然可以继续解析您离开的位置。还没有一个国家被摧毁。
在C#中,您当然可以更自由地将其变为更灵活的错误转换和报告管道,您可以在其中转换严格的读者,例如:所有警告错误。
您需要走哪条路线取决于您的图书馆的使用方式以及图书馆用户的熟练程度以及您想要处理多少服务请求,因为有些用户会犯愚蠢的错误。在尽可能严格(可能过于严格并且你得到一个难以使用的库)或过于放松(很好的解析器但由于内部错误而默默地跳过一半输入)之间总是需要权衡。
Windows SDK库有时候很难使用,因为那里的工程师会针对不太严格的呼叫进行优化。他们向您抛出Win32错误代码或HResult,您必须弄清楚您违反了哪个原则(内存对齐,缓冲区大小,交叉线程......)。
答案 2 :(得分:1)
我认为事件订阅模式没问题。但你可以考虑接口。这可能会给你更多的灵活性。像这样:
public interface IMessageHandler
{
void HandleMessage(object sender, MessageAvailaibleEventArgs eventArgs);
}
public abstract class ModelReader : IDisposable
{
private readonly IMessageHandler handler; // Should be initialized somewhere, e.g. in constructor
public abstract Model Read();
public event EventHandler<MessageAvailableEventArgs> MessageAvailable;
protected virtual void RaiseError(string message)
{
MessageAvailaibleEventArgs eventArgs =
new MessageAvailaibleEventArgs(TraceEventType.Error, message);
this.handler.HandleMessage(this, eventArgs);
}
}
现在,您可以使用消息处理程序的任何实现,例如,您的事件订阅:
public class EventMessageHandler : IMessageHandler
{
public event EventHandler<MessageAvailaibleEventArgs> MessageAvailable;
public void HandleMessage(object sender, MessageAvailaibleEventArgs eventArgs)
{
var handler = this.MessageAvailable;
if (handler != null)
{
handler(this, new MessageAvailaibleEventArgs(
TraceEventType.Error, message);
}
}
}
答案 3 :(得分:1)
您当前的方法并不是最干净的模型,因为它有两种相互矛盾的执行方式:pull(读取输入)和push(处理错误回调),这意味着您的读者和客户都需要更多的状态管理来提供连贯的整体。
我建议你避免使用类似XmlReader的界面,并使用访问者模式将输入数据和错误推送到客户端应用程序。
答案 4 :(得分:0)
如果有错误阻止库完成它的工作,我会使用异常。
如果这是严格用于跟踪和检测,我会选择你的模式或TextWriter模式,消费者会传入文本编写器,你会将跟踪信息写入其中,唯一的问题就是你可以只有一个外部用户。但它会导致代码稍微清晰。
public TextWriter Log { get; set; }
private void WriteToLog(string Message)
{
if (Log != null) Log.WriteLine(message);
}
答案 5 :(得分:0)
我知道您提到过您可能有多个订阅者,因此事件处理程序和Injected接口调用都是很好的解决方案,如上所述。为了完整性,我还将提供一个可选的Read()参数作为Func。例如:
void Read(Func<string, bool> WarningHandler)
{
bool cancel = false;
if (HasAWarning)
cancel = WarningHandler("Warning!");
}
然后当然你可以在Func代表中做任何你想做的事情,比如警告几个来源。但是当与事件模型结合使用时,您可以以一般方式处理所有警告(可能像记录器一样),并使用Func进行单独的专门操作和控制流程(如果需要)。