过滤器是仅在某些情况下应应用的模式。 在原始帖子中 ,我提供了一个非常简单的示例,旨在演示如何应用它。 在这篇文章中,我提供了一个更详细的示例,该示例还旨在说明何时以及为什么应用它。
介绍
该职位包括以下10个简短步骤 。 在每个步骤中,我介绍了以下两种类型的要求 :
- B- * : 业务要求(由产品所有者提供→ 无可争辩 )
- S- * : 解决方案要求(从解决方案的选择→ 有争议的结果中得出)
我提出了一个Java模型,该模型可以满足到目前为止介绍的要求。 我一直这样做,直到Filterer
成为首选解决方案。
所以,让我带你踏上这段旅程……
步骤1:问题检测器
要求#1
假设企业要求一种算法来检测英文文本中的语法和拼写问题 。
例如:
- 文字: 您很了解。 →要检测的问题:
- migth (类型:拼写)
- 文字: 我没有注意放松。 →要检测的问题:
- 注意 (类型:拼写)
- 松散 (类型:语法)
- 文字: 我一直注意到它很松。 →要检测的问题:∅
这是第一个业务需求 ( B-1 )。
B-1会议的最简单模型可以是:
- 输入 :纯文本
- 输出 :问题列表,每个问题提供:
- 输入文字内的偏移量
这是我们的第一个解决方案要求 ( S-1 )。
Java模型#1
我们可以将S-1建模为:
interface IssueDetector {// e.g. text: "You migth know it."List<Issue> detect(String text);
}
哪里:
interface Issue {int startOffset(); // e.g. 4 (start of "migth")int endOffset(); // e.g. 9 (end of "migth")IssueType type(); // e.g. SPELLING
}
enum IssueType { GRAMMAR, SPELLING }
提交1 。
步骤2:机率
要求#2
但是,要实现以这种确定性方式工作的真实IssueDetector
相当困难:
- 问题(概率P = 100% )
- 无问题(概率P = 0% )
相反, IssueDetector
应该是概率性的 :
- 可能的问题(概率P =? )
我们可以通过引入概率阈值( PT )来保持问题/非问题的区别:
- 问题(概率P≥PT ),
- 非问题(概率P <PT )。
尽管如此,仍然值得修改模型以保留概率( P )–例如,在渲染 (更高的概率→更突出的渲染)中很有用。
综上,我们额外的解决方案要求是:
- S-2 :支持发布概率( P );
- S-3 :支持概率阈值( PT )。
Java模型2
通过将probability()
添加到Issue
我们可以满足S-2 :
interface Issue {// ...double probability();
}
我们可以通过在IssueDetector
添加probabilityThreshold
IssueDetector
probabilityThreshold
来满足S-3的IssueDetector
:
interface IssueDetector {List<Issue> detect(String text, double probabilityThreshold);
}
提交2 。
步骤3:可能的问题
要求#3
假设业务需要 :
- B-3 :使用英语语言家校对的文本测试所有问题检测器(=无概率)。
这样的校对文本 (或: 测试用例 )可以定义为:
- 文字,例如您shuold知道。
- 预期的问题,例如
- shuold (类型:拼写)
因此,我们的解决方案要求是:
- S-4 :支持预期的问题(=没有可能性)。
Java模型3
我们可以通过提取子接口( ProbableIssue
)来满足S-4 :
interface ProbableIssue extends Issue {double probability();
}
并从IssueDetector
返回ProbableIssue
:
interface IssueDetector {List<ProbableIssue> detect(...);
}
提交3 。
步骤4:明智的文字
要求#4
假使,假设:
- 所有测试用例都在外部定义(例如,在XML文件中);
- 我们要创建一个参数化的JUnit测试 ,其中参数是作为
Stream
提供的 测试用例 。
通常,一个测试用例代表了一种我们可以称之为按问题发布的文本 (一种文本及其问题)。
为了避免将有问题的文本建模为Map.Entry<String, List<Issue>>
(含糊不清,表示抽象不足),让我们介绍另一个解决方案要求 :
- S-5 :支持明智的文本。
Java模型4
我们可以将S-5建模为:
interface IssueWiseText {String text(); // e.g. "You migth know it."List<Issue> issues(); // e.g. ["migth"]
}
这使我们可以简单地定义测试用例Stream
,如下所示:
-
Stream<IssueWiseText>
代替
-
Stream<Map.Entry<String, List<Issue>>>
。
提交4 。
步骤5:预期覆盖率
要求#5
假设业务需要 :
- B-4 :报告一系列测试用例的 预期 问题范围 ;
为了简单起见,将问题的覆盖范围定义为:
总发行时长
─────────────
总文字长度
实际上, 问题的覆盖范围可能代表一些非常复杂的业务逻辑 。
Java模型5
我们可以使用基于Collector
的方法处理B-4 :
static double issueCoverage(Stream<? extends IssueWiseText> textStream) {return textStream.collect(IssueCoverage.collector());
}
Collector
基于具有两个可变字段的Accumulator
:
int totalIssueLength = 0;
int totalTextLength = 0;
对于每个IssueWiseText
,我们增加:
totalIssueLength += issueWiseText.issues().stream().mapToInt(Issue::length).sum();
totalTextLength += issueWiseText.text().length();
然后我们将问题覆盖范围计算为:
(double) totalIssueLength / totalTextLength
提交5 。
步骤6:取得覆盖
要求#6
假设业务需要 :
- B-5 :报告获得了整个测试集的问题覆盖率 。
“获得”是指“使用检测到的问题进行计算”。 现在事情开始变得有趣了!
首先,由于IssueCoverage
表示业务逻辑 ,所以我们不应该重复它:
- S-6 :重用问题覆盖代码。
其次,由于该方法采用Stream<? extends IssueWiseText>
Stream<? extends IssueWiseText>
,我们需要为ProbableIssue
建模一个IssueWiseText
:
- S-7 :支持概率问题文本。
我在这里只看到两个选择:
- 参数化 :
IssueWiseText<I extends Issue>
; - 子类型化 :
ProbabilisticIssueWiseText extends IssueWiseText
。
参数Java模型#6
S-7的参数模型很简单-我们在IssueWiseText
需要<I extends Issue>
IssueWiseText
<I extends Issue>
(一个有界类型参数 ):
interface IssueWiseText<I extends Issue> {String text();List<I> issues();
}
这个模型有缺点(例如类型擦除 ),但是很简洁。
我们还可以修改IssueDetector
来返回IssueWiseText<ProbableIssue>
。
此外,我们的测试用例Stream
可能会变成Stream<IssueWiseText<Issue>>
(尽管IssueWiseText<Issue>
颇有争议)。
提交6a 。
子类型化Java模型#6
另一个选择是选择子类型 (它有其自身的缺点,其中最大的缺点可能是重复 )。
S-7的子类型模型采用返回类型协方差 :
interface ProbabilisticIssueWiseText extends IssueWiseText {@OverrideList<? extends ProbableIssue> issues();
}
IssueWiseText
中的issues()
必须成为上限 ( List<? extends Issue>
)。
我们还可以修改IssueDetector
来返回ProbabilisticIssueWiseText
。
提交6b 。
步骤7:按问题类型过滤
要求#7
假设业务需要 :
- B-6 :按问题类型报告问题范围 。
我们可以通过接受Predicate <? super Issue>
类型的额外参数来支持它Predicate <? super Issue>
Predicate <? super Issue>
( IssueType
参数通常太窄)。
但是,直接在IssueCoverage
支持它会使业务逻辑复杂化( 提交7a' )。 相反,我们宁愿将已过滤的IssueWiseText
实例提供给IssueCoverage
。
我们如何进行过滤? “手动”执行操作(调用new
)会给实现带来不必要的耦合(我们甚至还不了解它们)。 这就是为什么我们让IssueWiseText
进行过滤的原因(我觉得这个逻辑属于那里):
- S-8 :支持在
IssueWiseText
按Issue
进行IssueWiseText
。
换句话说,我们希望能够说:
换句话说,我们希望能够说:
嘿
IssueWiseText
,按Issue
筛选自己!
参数Java模型#7
在参数模型中,我们将以下filtered
方法添加到IssueWiseText<I>
IssueWiseText<I> filtered(Predicate<? super I> issueFilter);
这使我们满足B-6的要求:
return textStream.map(text -> text.filtered(issue -> issue.type() == issueType)).collect(IssueCoverage.collector());
提交7a 。
子类型化Java模型#7
在子类型模型中,我们还添加了filtered
方法(与上面的方法非常相似):
IssueWiseText filtered(Predicate<? super Issue> issueFilter);
这让我们以与上述相同的方式遇到了B-6 。
提交7b 。
步骤8:按机率筛选
要求#8
假设业务需要 :
- B-7 :按最小概率报告问题的覆盖范围 。
换句话说,企业希望知道概率分布如何影响问题的覆盖范围。
现在,我们不希望运行IssueDetector
与许多不同的概率阈值(PT),因为这将会是非常低效的。 相反,我们将只运行一次( PT = 0 ),然后以最低的概率继续丢弃问题,以重新计算问题的覆盖范围。
但是,为了能够按概率进行过滤,我们需要:
- S-9 :支持在概率问题文本中按
ProbableIssue
进行过滤。
参数Java模型#8
在参数模型中,我们不需要更改任何内容。 我们可以满足B-7的要求:
return textStream.map(text -> text.filtered(issue -> issue.probability() >= minProbability)).collect(IssueCoverage.collector());
提交8a 。
子类型化Java模型#8
在子类型化模型中,这比较困难,因为我们需要在ProbabilisticIssueWiseText
一个额外的方法:
ProbabilisticIssueWiseText filteredProbabilistic(Predicate<? super ProbableIssue> issueFilter);
让我们满足B-7的要求:
return textStream.map(text -> text.filteredProbabilistic(issue -> issue.probability() >= minProbability)).collect(IssueCoverage.collector());
提交8b 。
对我来说, ProbabilisticIssueWiseText
这种额外方法非常令人不安(请参阅此处 )。 这就是为什么我提议…
步骤9:筛选器
要求#9
由于子类型模型中的常规过滤是如此“不一致”,因此让我们使其统一:
- S-10 :在问题型文本的子类型模型中支持统一过滤。
换句话说,我们希望能够说:
嘿
ProbabilisticIssueWiseText
,用ProbableIssue
过滤自己(但是与IssueWiseText
本身通过Issue
过滤自己一样)!
据我所知,这只能通过Filterer Pattern实现 。
子类型化Java模型#9
因此,我们将通用 Filterer
应用于IssueWiseText
:
Filterer<? extends IssueWiseText, ? extends Issue> filtered();
以及ProbablisticIssueWiseText
:
@Override
Filterer<? extends ProbabilisticIssueWiseText, ? extends ProbableIssue> filtered();
现在,我们可以通过调用以下内容进行统一过滤:
text.filtered().by(issue -> ...)
提交9 。
步骤10:检测时间
到这个时候,您必须想知道如果参数化模型这么简单的话,为什么还要打扰子类型化模型。
因此,最后一次,我们假设业务需要 :
- B-8 :报告检测时间 (=检测给定文本中所有问题所花费的时间)。
参数Java模型#10
我仅看到将B-8合并到参数模型中的两种方法:1)组成,2)子类型化。
参数Java模型#10的组成
涂抹组合物很容易。 我们介绍IssueDetectionResult
:
interface IssueDetectionResult {IssueWiseText<ProbableIssue> probabilisticIssueWiseText();Duration detectionTime();
}
并修改IssueDetector
以将其返回。
提交10a 。
参数Java模型#10的子类型化
应用子类型需要更多的工作。 我们需要添加ProbabilisticIssueWiseText<I>
*
interface ProbabilisticIssueWiseText<I extends ProbableIssue> extends IssueWiseText<I> {Duration detectionTime();// ...
}
并修改IssueDetector
以返回ProbabilisticIssueWiseText<?>
。
提交10a' 。
*请注意,我在ProbabilisticIssueWiseText
上保留了<I>
,以便不以危险的方式将参数化与子类型化相关联 。
子类型化Java模型#10
使用纯子类型模型,合并B-8非常容易。 我们只是将detectionTime()
添加到ProbabilisticIssueAwareText
:
interface ProbabilisticIssueWiseText extends IssueWiseText {Duration detectionTime();// ...
}
提交10b 。
结论
没有时间详细讨论了(该帖子已经比我预期的要长)。
但是,与其他解决方案Filterer
,我更喜欢纯子类型化(因此更喜欢Filterer
),因为:
- 具有组合的参数化使我没有通用的超类型(在某些情况下,这是一个问题);
- 具有子类型的参数化具有太多的自由度。
我说“太多自由度”,我只需要:
-
IssueAwareText<?>
-
ProbabilisticIssueAwareText<?>
-
IssueAwareText<Issue>
(有争议)
但在代码中,我也会遇到(根据经验说!):
-
IssueAwareText<? extends Issue>
IssueAwareText<? extends Issue>
(冗余上限) -
IssueAwareText<ProbableIssue>
-
IssueAwareText<? extends ProbableIssue>
IssueAwareText<? extends ProbableIssue>
(为什么不ProbabilisticIssueAwareText<?>
?) -
ProbabilisticIssueAwareText<? extends ProbableIssue>
ProbabilisticIssueAwareText<? extends ProbableIssue>
(冗余上限) -
ProbabilisticIssueAwareText<ProbableIssue>
所以对我来说太混乱了。 但是,如果您真的对此主题感兴趣,请查看“ 复杂子类型与参数化” (不过请注意,它甚至比这篇文章还要长!)。
感谢您的阅读!
翻译自: https://www.javacodegeeks.com/2019/02/filterer-pattern-10-steps.html