2010-10-25 2 views
3

У меня есть библиотека, которая имеет два входных формата для объектной модели, описанной библиотекой. В настоящее время я использую модель подписки на события для повышения ошибок/предупреждений/подробных сообщений для конечного пользователя библиотеки. Это не оказалось самой чистой моделью, и мне было интересно, существует ли соответствующий шаблон дизайна или что-то подобное в .NET Framework (когда в Риме), чтобы лучше справляться с этой ситуацией.Шаблон для повышения ошибок/предупреждений при разборе в библиотеке

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

Edit: некоторые разъяснения. Процедура Read будет работать с ошибкой Fatal при использовании исключения. Сообщения записываются в потенциально несколько источников с конца пользователя, поэтому любая модель должна избегать ограничения количества потенциальных источников.

ответ

3

Я могу дать вам реальный пример. Библиотека Html Agility Pack представляет собой библиотеку синтаксического анализа. Он просто определяет список ошибок синтаксического анализа в классе читателя. Расширение вашего примера, это будет что-то вроде:

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 
    { 
     ... 
    } 

И вы все еще можете добавить событие, если библиотека абонент хочет на лету события.

+0

Эй. Я был бы признателен, если вы добавите отказ от ответов на ответы, в которых вы рекламируете пакет Agility Pack, отметив, что вы его автор. Я вижу, что есть такая группа, в которой вы в настоящее время не раскрываете свою принадлежность - по адресу http://stackoverflow.com/a/4601681/1709587, http://stackoverflow.com/a/5229972/1709587, http: //stackoverflow.com/a/5441683/1709587, http://stackoverflow.com/a/13126335/1709587, http://stackoverflow.com/a/5145916/1709587 и здесь. –

+0

Я ничего здесь не рекламирую. Вы можете видеть мою «принадлежность», как вы говорите, если вы нажмете на меня как на пользователя. –

+0

Хм, извините - вы правы, что этот конкретный пост на самом деле не рекомендует использовать Html Agility Pack, просто говоря об этом случайно. Возможно, я должен был внимательно прочитать перед комментированием. Тем не менее, мой запрос означает все связанные сообщения. –

0

Если есть ошибка, из-за которой библиотека не выполняет свою работу, я бы использовал исключение.

Если это строго для отслеживания и контрольно-измерительных приборов, я бы либо пошел с вашим шаблоном, либо с шаблоном TextWriter, где потребитель передал бы текстовый автору, и вы напишете информацию о трассировке, единственная проблема с этим - это вы может иметь только один внешний абонент. но в результате получается несколько более чистый код.

public TextWriter Log { get; set; } 

private void WriteToLog(string Message) 
{ 
    if (Log != null) Log.WriteLine(message); 
} 
+0

Я немного разъясню порядок обработки и регистрации исключений. – user7116

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

Итак, теперь вы можете использовать любую реализацию обработчика сообщений, для exmaple подписки события одного:

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

Интересно, все еще потенциально ограничивает меня одной раковиной. – user7116

+0

Привет @sixlettervariables! Имейте в виду, что вы могли бы собрать экземпляры IMessageHandler вместе, чтобы получить желаемое поведение. Это то, что вы имели в виду? – nick2083

1

Ваш нынешний подход не является самой чистой моделью, поскольку он имеет два противоречивых стиля исполнения, тянет (чтение ввода) и push (обратные вызовы обработки ошибок), а это означает, что и ваш читатель, и его клиенты требуют большего государственного управления для предоставления цельное целое.

Я рекомендую вам избегать интерфейса XmlReader и использовать шаблон посетителя для ввода входных данных и ошибок в клиентское приложение.

+0

Как и в случае с сообщением 'ModelReader', я предоставляю' MessageVisitor'? Похоже на то же, что и на обработчике событий для моего приложения. Модель описана в файле ASCII длиной 50-60 тыс. И включает около 25-30 объектов высокого уровня. На самом деле нет никакого способа отделить чтение описания модели ASCII в отдельных сущностях либо с использованием подхода 'ModelPartVisitor'. – user7116

+0

Обработчики событий - это обобщенная форма посетителя, где структура является линейной. Когда вы читаете каждый объект из «ModelReader», не используете ли вы какую-либо логику для ветвления на разные процессоры сущностей? Они не подходят кандидатам на методы, которые вызывается «ModelReader»? Я не понимаю, что вы видите как препятствие в улучшении вашей модели.Цель, по крайней мере на начальном этапе, должна состоять в том, чтобы просто перемещать как чтение, так и обработку ошибок в единую модель (будь то push или pull). – Huperniketes

+0

Проблема заключается в том, что сущности не застроены за один проход, и обычно требуется 2-3 прохода для правильной сборки. – user7116

2

Вам, похоже, нужен составной валидатор, в котором ваши пользователи могут подключать свою собственную логику, чтобы обозначать конкретные нефатальные ошибки как фатальные, предупреждающие или информационные сообщения. Выброс исключений не соответствует законопроекту, поскольку вы уже оставили этот метод, если другая часть хотела превратить его в предупреждение, но захотела продолжить синтаксический анализ. Это звучит так же, как две модели обработки исключений для пропуска в Windows для обработки структурированных исключений. Это в основном идет как

  1. Регистрация как много обработчиков исключений, как вы хотите
  2. При обнаружении ошибки в проходе все обработчики просят (не Exeption не бросать же!), Который один хочет обработать ошибку. Первый, который говорит «да», станет фактическим обработчиком, который решает, что делать.
  3. Когда был найден обработчик, который может справиться, мы пройдем 2 и позвоним ему. На этот раз это исключение - время броска или нет. Это полностью зависит от обработчика.

Красота этой двухпроходной модели заключается в том, что во время первого прохода все обработчики задаются, но вы по-прежнему можете продолжить синтаксический анализ, где вы ушли. Ни одно государство еще не уничтожено.

В C# у вас, конечно, гораздо больше свободы, чтобы превратить это в более гибкое преобразование ошибок и конвейер отчетов, где вы можете преобразовать для строгого читателя, например. все предупреждения к ошибке.

Какой маршрут вам нужно, зависит от того, как ваша библиотека используется и насколько квалифицированы пользователи вашей библиотеки и сколько запросов на обслуживание вы хотите обрабатывать, поскольку некоторые пользователи делают немые ошибки. Всегда существует компромисс между beeing настолько строгим, насколько это возможно (возможно, слишком строгим, и вы получаете сложную библиотеку) или слишком расслабленным (симпатичный парсер, но молча пропускает половину моего ввода из-за внутренних ошибок).

Библиотеки Windows SDK иногда довольно сложны в использовании, потому что инженеры там оптимизируют больше к менее звонящим вызовам. Они бросают код ошибки Win32 или HResult на вас, и вы должны выяснить, какой принцип (выравнивание памяти, размер буфера, перекрестная резьба, ...) вы нарушили.

0

Я знаю, что вы упомянули, что у вас может быть несколько подписчиков, поэтому обработчики событий и вызов интерфейса Injected являются хорошими решениями, как уже упоминалось выше. Для полноты я также предлагаю предоставить дополнительный параметр Read() в качестве Func. например:

void Read(Func<string, bool> WarningHandler) 
    { 
     bool cancel = false; 
     if (HasAWarning) 
      cancel = WarningHandler("Warning!"); 
    } 

Тогда, конечно, вы можете делать все, что вы хотели в делегат Func, как divy предупреждение из нескольких источников. Но в сочетании с моделью «Событие» вы можете обрабатывать все предупреждения в общем виде (например, логгером) и использовать Func для отдельных специализированных действий и потока управления (при необходимости).

Смежные вопросы