Добавление функциональности в любой TextReader - PullRequest
4 голосов
/ 30 марта 2010

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

Я хочу добавить отслеживание местоположения в TextReader экземпляр. Таким образом, читая токены, я могу захватить местоположение (которое обновляется TextReader при считывании данных) и передать его токену в процессе токенизации.

Я ищу хороший подход к достижению этой цели. Я придумал несколько проектов.

Ручное отслеживание местоположения

Каждый раз, когда мне нужно прочитать из TextReader, я вызываю AdvanceString на объекте Location токенизатора со считанными данными.

Преимущества

  • Очень просто.
  • не раздуваться.
  • Нет необходимости переписывать TextReader методы.

Недостатки

  • Соединяет логику отслеживания местоположения с процессом токенизации.
  • Легко забыть отследить что-то (хотя модульное тестирование помогает в этом).
  • Расширяет существующий код.

Обычная TextReader оболочка

Создайте класс LocatedTextReaderWrapper, который окружает каждый вызов метода, отслеживая свойство Location. Пример:

public class LocatedTextReaderWrapper : TextReader {
    private TextReader source;

    public Location Location {
        get;
        set;
    }

    public LocatedTextReaderWrapper(TextReader source) :
        this(source, new Location()) {
    }

    public LocatedTextReaderWrapper(TextReader source, Location location) {
        this.Location = location;
        this.source = source;
    }

    public override int Read(char[] buffer, int index, int count) {
        int ret = this.source.Read(buffer, index, count);

        if(ret >= 0) {
            this.location.AdvanceString(string.Concat(buffer.Skip(index).Take(count)));
        }

        return ret;
    }

    // etc.
}

Преимущества

  • Токенизация не знает об отслеживании местоположения.

Недостатки

  • Пользователю необходимо создать и утилизировать экземпляр LocatedTextReaderWrapper в дополнение к своему экземпляру TextReader.
  • Не позволяет добавлять разные типы трекинга или трекеры местоположения без слоев оболочек.

На основе событий TextReader Оболочка

Как и LocatedTextReaderWrapper, но отделяет его от объекта Location, вызывающего событие при чтении данных.

Преимущества

  • Может использоваться повторно для других типов отслеживания.
  • Tokenization не знает об отслеживании местоположения или другом отслеживании.
  • Может иметь одновременно несколько независимых Location объектов (или других методов отслеживания).

Недостатки

  • Требуется стандартный код для включения отслеживания местоположения.
  • Пользователю необходимо создать и удалить экземпляр оболочки, в дополнение к своему экземпляру TextReader.

Аспектно-ориентированный подход

Используйте AOP, чтобы работать как подход обертки на основе событий.

Преимущества

  • Может использоваться повторно для других типов отслеживания.
  • Tokenization не знает об отслеживании местоположения или другом отслеживании.
  • Нет необходимости переписывать TextReader методы.

Недостатки

  • Требуются внешние зависимости, которых я хочу избежать.

Я ищу лучший подход в моей ситуации. Я хотел бы:

  • Не разбрасывать методы токенизатора с отслеживанием местоположения.
  • Не требует тяжелой инициализации в коде пользователя.
  • Не иметь / много шаблонного / дублированного кода.
  • (возможно) не соединяет TextReader с классом Location.

Любое понимание этой проблемы и возможные решения или корректировки приветствуются. Спасибо!

(Для тех, кому нужен конкретный вопрос: как лучше всего обернуть функциональность TextReader?)

Я реализовал подходы «Обычная TextReader оболочка» и «Оболочка на основе событий TextReader» и недоволен обоими по причинам, упомянутым в их недостатках.

Ответы [ 3 ]

5 голосов
/ 06 апреля 2010

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

Лучшая упаковка: Вы можете написать это более простым способом, чтобы отделить TextReader от типа Location - таким образом, вы можете легко изменить Location независимо или даже предоставить другие функции, которые на основе отслеживания прогресса чтения.

interface ITracker {
  void AdvanceString(string s);
}

class TrackingReader : TextReader {
  private TextReader source;
  private ITracker tracker;
  public TrackingReader(TextReader source, ITracker tracker) {
    this.source = source;
    this.tracker = tracker;
  }
  public override int Read(char[] buffer, int index, int count) {
    int res = base.Read(buffer, index, count);
    tracker.AdvanceString(buffer.Skip(index).Take(res);
    return res;
  }
}

Я бы также включил создание трекера в некоторый фабричный метод (чтобы у вас было одно место в приложении, которое имеет дело со строительством). Обратите внимание, что вы можете использовать этот простой дизайн для создания TextReader, который сообщает о прогрессе нескольким Location объектам:

static TextReader CreateReader(TextReader source, params ITracker[] trackers) {
   return trackers.Aggregate(source, (reader, tracker) =>
       new TrackingReader(reader, tracker));
}

Это создает цепочку из TrackingReader объектов, и каждый объект сообщает о ходе чтения одному из трекеров, переданных в качестве аргументов.

По поводу событий : я думаю, что использование стандартных событий .NET / C # для такого рода вещей не так часто делается в библиотеках .NET, но я думаю, что этот подход может быть довольно интересным тоже - особенно в C # 3, где вы можете использовать такие функции, как лямбда-выражения или даже Reactive Framework.

Простое использование не добавляет столько плиты котла:

using(ReaderWithEvents reader = new ReaderWithEvents(source)) {
  reader.Advance += str => loc.AdvanceString(str);
  // ..
}

Однако вы также можете использовать Reactive Extensions для обработки событий. Чтобы подсчитать количество новых строк в исходном тексте, вы можете написать что-то вроде этого:

var res =
  (from e in Observable.FromEvent(reader, "Advance")
   select e.Count(ch => ch == '\n')).Scan(0, (sum, current) => sum + current);

Это даст вам событие res, которое срабатывает каждый раз, когда вы читаете какую-то строку из TextReader, и значение, переносимое событием, будет текущим номером строки (в тексте).

1 голос
/ 30 марта 2010

Я бы выбрал простую оболочку TextReader, потому что это кажется наиболее естественным подходом. Кроме того, я не думаю, что упомянутые вами недостатки действительно являются недостатками:

  • В дополнение к своему экземпляру TextReader пользователю необходимо создать и разместить экземпляр LocationTextReaderWrapper.

Что ж, если пользователю нужно отслеживать, ему все равно придется манипулировать объектами, связанными с отслеживанием, и создание оболочки не так уж сложно ... Чтобы упростить задачу, вы можете создать метод расширения, который трекинговая оболочка для любого TextReader.

  • Не позволяет добавлять разные типы трекинга или трекеры местоположения без слоев оболочек.

Вы всегда можете использовать коллекцию Location объектов, а не один:

public class LocatedTextReaderWrapper : TextReader {

    private TextReader source;

    public IList<Location> Locations {
        get;
        private set;
    }

    public LocatedTextReaderWrapper(TextReader source, params Location[] locations) {
        this.Locations = new List<Location>(locations);
        this.source = source;
    }

    public override int Read(char[] buffer, int index, int count) {
        int ret = this.source.Read(buffer, index, count);

        if(ret >= 0) {
            var s = string.Concat(buffer.Skip(index).Take(count));
            foreach(Location loc in this.Locations)
            {
                loc.AdvanceString(s);
            }
        }

        return ret;
    }

    // etc.
}
0 голосов
/ 08 апреля 2010

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

...