我有一个Location类,它表示流中某个位置。(类没有耦合到任何特定流。)位置信息将用于将标记与解析器中输入的位置匹配,以便更好地向用户报告错误。
我想将位置跟踪添加到TextReader实例。这样,在读取标记时,我可以获取位置(在读取数据时由TextReader更新)并在标记化过程中将其提供给标记。
我在寻找一个实现这个目标的好方法。我想出了几个设计方案。
手动位置跟踪
每次需要从TextReader中读取数据时,我都会在标记器的AdvanceString对象上调用Location
优势
很简单。
没有阶级膨胀。
无需重写TextReader方法。
缺点
将位置跟踪逻辑与标记化过程耦合。
很容易忘记跟踪某些东西(尽管单元测试有助于实现这一点)。
扩展现有代码。
普通包装纸
创建一个包围每个方法调用的TextReader类,跟踪一个LocatedTextReaderWrapper属性。例子:

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

优势
标记化不知道位置跟踪。
缺点
除了Location实例之外,用户还需要创建和释放LocatedTextReaderWrapper实例。
不允许在没有包装层的情况下添加不同类型的跟踪或不同位置的跟踪程序。
基于事件的包装器
类似于TextReader,但将其与每当读取数据时引发事件的TextReader对象分离。
优势
可用于其他类型的跟踪。
标记化不知道位置跟踪或其他跟踪。
可以同时对多个独立的对象(或其他跟踪方法)进行跟踪。
缺点
需要样板代码才能启用位置跟踪。
除了LocatedTextReaderWrapper实例之外,用户还需要创建和释放包装器实例。
面向方面的方法
使用aop执行类似于基于事件的包装器方法的操作。
优势
可用于其他类型的跟踪。
标记化不知道位置跟踪或其他跟踪。
无需重写Location方法。
缺点
需要外部依赖项,我希望避免这种情况。
我在寻找适合我情况的最佳方法。我想:
不使用位置跟踪来扩展标记器方法。
不需要在用户代码中进行大量初始化。
没有任何/很多样板文件/重复代码。
(也许)不要把LocationTextReader类结合起来。
欢迎对这个问题的任何深入了解和可能的解决方案或调整。谢谢!
(对于那些想要一个特定问题的人:包装TextReader功能的最佳方式是什么?)
我已经实现了“plainTextReaderwrapper”和“event-basedLocationwrapper”两种方法,出于缺点中提到的原因,我对这两种方法都不满意。

最佳答案

我同意创建一个实现TextReader的包装器,将实现委托给底层TextReader并添加一些对位置跟踪的额外支持是一个好方法。您可以将其视为Decorator pattern,因为您正在用一些附加功能装饰TextReader类。
更好的包装:您可以用一种更简单的方式将TextReaderLocation类型分离,这样您就可以轻松地独立修改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库中使用标准.NET/C事件并不像在.NET库中那样频繁,但我认为这种方法可能也很有趣—特别是在C 3中,您可以使用诸如lambda表达式甚至反应式框架之类的功能。
简单使用不会增加那么多锅炉板:
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中读取某个字符串时都会触发该事件,并且该事件所携带的值将是当前行数(在文本中)。

10-05 19:02