向任何TextReader添加功能

时间:2010-03-30 19:19:35

标签: c# oop aop

我有一个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对象解耦,从而引发事件。

优点

  • 可以重复用于其他类型的跟踪。
  • 令牌化不了解位置跟踪或其他跟踪。
  • 可以同时跟踪多个独立Location个对象(或其他跟踪方法)。

缺点

  • 需要样板代码才能启用位置跟踪。
  • 除了TextReader实例外,用户还需要创建和处理包装器实例。

面向方面的方法

使用AOP执行类似于基于事件的包装器方法。

优点

  • 可以重复用于其他类型的跟踪。
  • 令牌化不了解位置跟踪或其他跟踪。
  • 无需重写TextReader方法。

缺点

  • 需要外部依赖项,我想避免这种情况。

我正在寻找最适合我的情况。我想:

  • 不会使用位置跟踪扩充tokenizer方法。
  • 用户代码中不需要大量初始化。
  • 没有任何/很多样板/重复代码。
  • (也许)不会将TextReaderLocation类结合在一起。

欢迎任何有关此问题的见解以及可能的解决方案或调整。谢谢!

(对于那些想要特定问题的人:包裹TextReader功能的最佳方式是什么?)

我已经实现了“普通TextReader包装器”和“基于事件的TextReader包装器”方法,并且由于其缺点中提到的原因而对两者都不满意。 < / p>

3 个答案:

答案 0 :(得分:5)

我同意创建一个实现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#事件并不常见,但我认为这种方法可能相当也很有趣 - 特别是在C#3中你可以使用lambda表达式甚至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读取一些字符串时都会触发该事件,并且事件所携带的值将是当前行数(在文本中)。< / p>

答案 1 :(得分:1)

我会选择普通的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.
}

答案 2 :(得分:0)

AFAIK,如果您使用PostSharp,它会将自身注入编译过程并根据需要重写代码,这样您就不会有任何永久的依赖关系(除了要求API用户使用PostSharp来编译您的源代码)。