代码之家  ›  专栏  ›  技术社区  ›  strager

向任何文本阅读器添加功能

  •  4
  • strager  · 技术社区  · 15 年前

    我有一个 Location 表示流中某个位置的类。(该类没有耦合到任何特定的流。)位置信息将用于将标记与我的解析器中输入的位置匹配,以允许向用户报告更好的错误。

    我想将位置跟踪添加到 TextReader 实例。这样,在读取令牌时,我可以获取位置(由 特克斯特雷德 当数据被读取时)并在标记化技术过程中将其提供给令牌。

    我正在寻找一种实现这一目标的好方法。我想出了几个设计方案。

    手动位置跟踪

    每次我需要阅读 特克斯特雷德 我打电话 AdvanceString 位置 具有数据读取的标记器的对象。

    优势

    • 很简单。
    • 没有班级膨胀。
    • 不需要重写 文本阅读器 方法。

    缺点

    • 将位置跟踪逻辑与标记化技术过程结合起来。
    • 容易忘记跟踪某些东西(尽管单元测试有助于实现这一点)。
    • 阻塞现有代码。

    平原 特克斯特雷德 包装器

    创建一个 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.
    }
    

    优势

    • 标记化技术不知道位置跟踪。

    缺点

    • 用户需要创建和释放 已定位的ExtreaderWrapper 例如,除了 特克斯特雷德 实例。
    • 不允许在没有包装层的情况下添加不同类型的跟踪或不同的位置跟踪。

    基于事件 特克斯特雷德 包装器

    喜欢 已定位的ExtreaderWrapper 但将其与 位置 每当读取数据时引发事件的对象。

    优势

    • 可用于其他类型的跟踪。
    • 标记化技术不知道位置跟踪或其他跟踪。
    • 可以有多个独立的 位置 对象(或其他跟踪方法)立即跟踪。

    缺点

    • 需要样板代码才能启用位置跟踪。
    • 用户需要创建和释放包装实例,除了 特克斯特雷德 实例。

    面向方面的方法

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

    优势

    • 可用于其他类型的跟踪。
    • 标记化技术不知道位置跟踪或其他跟踪。
    • 不需要重写 特克斯特雷德 方法。

    缺点

    • 需要外部依赖项,这是我想要避免的。

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

    • 不膨胀标记器方法和位置跟踪。
    • 不需要在用户代码中进行大量初始化。
    • 没有任何/多个样板文件/重复的代码。
    • (也许)不要把 特克斯特雷德 位置 班级。

    欢迎您深入了解此问题以及可能的解决方案或调整。谢谢!

    (对于那些想要一个特定问题的人:什么是包装 特克斯特雷德 ?)

    我已经实现了 特克斯特雷德 包装器“和”基于事件 特克斯特雷德 包装机的方法和我对两者都不满意,因为他们的缺点中提到的原因。

    3 回复  |  直到 15 年前
        1
  •  5
  •   Tomas Petricek    15 年前

    我同意创建一个实现 TextReader ,将实现委托给底层 特克斯特雷德 并且增加了一些对位置跟踪的额外支持是一种很好的方法。你可以认为这是 Decorator pattern 因为你在装饰 特克斯特雷德 用一些附加功能初始化。

    更好的包装: 你可以用一种更简单的方式来写它来分离你的 特克斯特雷德 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;
      }
    }
    

    我还将把跟踪器的创建封装到一些工厂方法中(这样您在处理构造的应用程序中就有一个单独的位置)。注意,您可以使用这个简单的设计来创建 特克斯特雷德 将进度报告给多个 位置 对象:

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

    这就产生了一系列 跟踪阅读器 对象和每个对象都向作为参数传递的一个跟踪程序报告读取进度。

    关于基于事件的 :我认为在.NET库中使用标准的.NET/C事件并不像在.NET库中那样频繁,但我认为这种方法可能也很有趣-特别是在C 3中,在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 每次从 特克斯特雷德 事件携带的值将是当前行数(在文本中)。

        2
  •  1
  •   Thomas Levesque    15 年前

    我要去平原 TextReader 包装,因为这似乎是最自然的方法。另外,我不认为你提到的缺点是真正的缺点:

    • 除了TextReader实例外,用户还需要创建和释放locatedExtreaderWrapper实例。

    好吧,如果用户需要跟踪,他无论如何都需要操纵与跟踪相关的对象,并且创建一个包装器并不是什么大问题…为了使事情更简单,您可以创建一个扩展方法,该方法为 特克斯特雷德 .

    • 不允许在没有包装层的情况下添加不同类型的跟踪或不同的位置跟踪。

    您可以使用 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.
    }
    
        3
  •  0
  •   RCIX    15 年前

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