设计图案为汇总列出了懒惰
-
03-07-2019 - |
题
我写的程序如下:
- 找到的所有文件正确的延长,在给定目录
- Foreach,找出现的所有给予串在那些文件
- 印每线
我想写这在功能方式,作为一系列的发电机的功能(东西那个电话 yield return
并且只有返回的一个项目的时间延迟装载),因此我的代码阅读这样的:
IEnumerable<string> allFiles = GetAllFiles();
IEnumerable<string> matchingFiles = GetMatches( "*.txt", allFiles );
IEnumerable<string> contents = GetFileContents( matchingFiles );
IEnumerable<string> matchingLines = GetMatchingLines( contents );
foreach( var lineText in matchingLines )
Console.WriteLine( "Found: " + lineText );
这都是好的,但是我也想要做的就是打印一些统计数据,在结束。事情是这样的:
Found 233 matches in 150 matching files. Scanned 3,297 total files in 5.72s
问题是,编写代码在一个纯粹的功能性'的风格如上所述,每个项目被延迟装载。
你只知道有多少文件相匹配,在总额,直至最终foreach循环完成,因为只有一个项目是有史以来 yield
ed在一个时间代码没有任何地方跟踪的如何许多的事情,它是找到以前。如果你调用皇宫的 matchingLines.Count()
方法,它将重新列举的集合!
我能想到的许多方法来解决这个问题,但他们似乎有点丑陋的。它袭击我的东西,人们就一定能有所做之前,我敢肯定还会有一个很好的设计图案显示了一种最佳做法的方式这样做。
任何想法?欢呼
解决方案
与此类似,其他的答案,但考虑一个稍微更加通用的办法...
...为什么不创建一个 装饰 类,可以用现有类型的执行和计算统计数字,因为它传递的其他项目通过。
这里有一个 Counter
类我只是扔在一起-但是你可以创建的变化对其他类型的聚集。
public class Counter<T> : IEnumerable<T>
{
public int Count { get; private set; }
public Counter(IEnumerable<T> source)
{
mSource = source;
Count = 0;
}
public IEnumerator<T> GetEnumerator()
{
foreach (var T in mSource)
{
Count++;
yield return T;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
foreach (var T in mSource)
{
Count++;
yield return T;
}
}
private IEnumerable<T> mSource;
}
你可以创建的三个实例 Counter
:
- 一个包裹
GetAllFiles()
计算总数的文件; - 一个包裹
GetMatches()
计算数量的匹配的文件;和 - 一个包裹
GetMatchingLines()
计算数量的匹配线。
关键用这种方法是,你不层次的多重责任上你现有课程/方法- GetMatchingLines()
方法只能处理的匹配,你就不要求它的轨道统计数据。
澄清 在回应评论 Mitcham
:
最后码会看起来像这样的东西:
var files = new Counter<string>( GetAllFiles());
var matchingFiles = new Counter<string>(GetMatches( "*.txt", files ));
var contents = GetFileContents( matchingFiles );
var linesFound = new Counter<string>(GetMatchingLines( contents ));
foreach( var lineText in linesFound )
Console.WriteLine( "Found: " + lineText );
string message
= String.Format(
"Found {0} matches in {1} matching files. Scanned {2} files",
linesFound.Count,
matchingFiles.Count,
files.Count);
Console.WriteLine(message);
注意,这仍然是一个功能处理方式-使用的变量是 不可改变的 (更多的喜欢 绑定的 比变量),和总体功能没有副作用。
其他提示
我想说你需要封装的进程进入一个'匹配'类中,你的方式捕获的统计数据,因为他们的进展。
public class Matcher
{
private int totalFileCount;
private int matchedCount;
private DateTime start;
private int lineCount;
private DateTime stop;
public IEnumerable<string> Match()
{
return GetMatchedFiles();
System.Console.WriteLine(string.Format(
"Found {0} matches in {1} matching files." +
" {2} total files scanned in {3}.",
lineCount, matchedCount,
totalFileCount, (stop-start).ToString());
}
private IEnumerable<File> GetMatchedFiles(string pattern)
{
foreach(File file in SomeFileRetrievalMethod())
{
totalFileCount++;
if (MatchPattern(pattern,file.FileName))
{
matchedCount++;
yield return file;
}
}
}
}
我会停止存在,因为我应该编码工作的东西,但总的想法是存在的。整个点'纯'功能的程序是没有副作用,而这种类型的静态计算的一个副作用。
我可以想到的两个想法
通过在一个上下文对象和返回(string+上下文),从你的调查员-纯粹的功能的解决方案
使用螺纹的地方储存,你的统计数据(CallContext),你可以花哨和支持一叠情况。所以你会像这样的代码.
using (var stats = DirStats.Create()) { IEnumerable<string> allFiles = GetAllFiles(); IEnumerable<string> matchingFiles = GetMatches( "*.txt", allFiles ); IEnumerable<string> contents = GetFileContents( matchingFiles ); stats.Print() IEnumerable<string> matchingLines = GetMatchingLines( contents ); stats.Print(); }
我带贝的代码和重它,直到我是内容。有趣的东西。
public class Counter
{
public int Count { get; set; }
}
public static class CounterExtensions
{
public static IEnumerable<T> ObserveCount<T>
(this IEnumerable<T> source, Counter count)
{
foreach (T t in source)
{
count.Count++;
yield return t;
}
}
public static IEnumerable<T> ObserveCount<T>
(this IEnumerable<T> source, IList<Counter> counters)
{
Counter c = new Counter();
counters.Add(c);
return source.ObserveCount(c);
}
}
public static class CounterTest
{
public static void Test1()
{
IList<Counter> counters = new List<Counter>();
//
IEnumerable<int> step1 =
Enumerable.Range(0, 100).ObserveCount(counters);
//
IEnumerable<int> step2 =
step1.Where(i => i % 10 == 0).ObserveCount(counters);
//
IEnumerable<int> step3 =
step2.Take(3).ObserveCount(counters);
//
step3.ToList();
foreach (Counter c in counters)
{
Console.WriteLine(c.Count);
}
}
}
输出为:21,3,3
假设这些职能是自己的,我唯一能想到的被访问者模式,通过在一个抽象的访问功能,叫你回来的时候每件事发生。例如:通过ILineVisitor入GetFileContents(其中我想打破了该文件进行)。ILineVisitor会一样的方法OnVisitLine(String),然后可以实现的ILineVisitor和使它保持适当的统计数据。冲洗和重复与ILineMatchVisitor,IFileVisitor等。或者你可以使用一个单一的IVisitor与OnVisit()方法,它有一个不同的语义在每一种情况。
您的职能将每个需要采取一个访问者,并呼吁它的OnVisit()在适当的时间,这可能似乎令人讨厌,但是至少有访客可以做很多有趣的事情,不仅仅是其他什么你在这里做.事实上,你其实可以避免编写GetMatchingLines通过一个访客检查的匹配在OnVisitLine(String线)的成GetFileContents.
这是一项丑陋的东西你已经考虑?