介绍
在我们的Java应用程序中复制/粘贴代码通常不好,但是有时这是不可避免的。 例如,项目License3j在Feature
类中为其支持的每种XXX
类型提供了一个isXXX
方法。 在这种情况下,我们要做的就是写
public boolean isBinary() {
return type == Type.BINARY;
}
public boolean isString() {
return type == Type.STRING;
}
public boolean isByte() {
return type == Type.BYTE;
}
public boolean isShort() {
return type == Type.SHORT;
} and so on
应用程序支持的每种功能类型。 那里有一些类型:Binary,String,Byte,Short,Int,Long,Float,Double,BigInteger,BigDecimal,Date,UUID。 键入所有非常相似的方法不仅是无聊的任务,而且容易出错。 很少有人擅长执行此类重复性任务。 为了避免这种情况,我们可以使用Java :: Geci框架,作为最简单的解决方案,我们可以使用生成器Iterate。
POM依赖
要使用生成器,我们必须添加依赖项
< dependency >
< groupId >com.javax0.geci</ groupId >
< artifactId >javageci-core</ artifactId >
< scope >test</ scope >
< version >1.4.0</ version > </ dependency >
该库仅在测试运行时执行,因此使用它并不意味着任何额外的依赖关系。 谁想要使用许可证3j库都不需要使用Java :: Geci。 这只是test
范围中使用的开发工具。
单元测试运行
依赖关系不会自行运行。 毕竟依赖不是程序。 它是打包到JAR中的一堆类文件,可在类路径上使用。 我们必须执行生成器,并且必须通过创建单元测试的框架来完成:
@Test
@DisplayName ( "run Iterate on the sources" )
void runIterate() throws IOException {
Geci geci = new Geci();
Assertions.assertFalse(
geci.register(Iterate.builder()
.define(ctx -> ctx.segment().param( "TYPE" , ctx.segment().getParam( "Type" ).orElse( "" ).toUpperCase()))
.build())
.generate()
, geci.failed()
);
}
它创建一个Geci
对象,使用生成器实例Geci
,然后在配置的框架Geci对象上调用generate()
。 就目前而言, define()
调用似乎有点神秘。 稍后我们将阐明这一点。
源代码准备
执行构建之前的最后一步是定义模板以及要插入模板的值。 无需编写所有方法,而是编写模板和编辑器折叠段:
/* TEMPLATE
LOOP Type=Binary|String|Byte|Short|Int|Long|Float|Double|BigInteger|BigDecimal|Date|UUID
public boolean is{{Type}}() {
return type == Type.{{TYPE}};
}
*/
//<editor-fold id="iterate">
//</editor-fold>
当我们通过框架执行生成器时,它将为占位符Type
每个值评估模板,并将每个{{Type}}
替换为实际值。 生成的代码将插入id
“ iterate”的编辑器折叠段中。
查看模板,您会发现有一个占位符{{TYPE}}
,未在列表中定义。 这是统一测试define()
进入图片的地方。 它定义一个使用上下文的使用者,并使用该上下文读取Type
的实际值,创建该值的大写版本并将其分配给名为TYPE
的段参数。
通常就是这样。 使用生成器还有其他功能,例如每次迭代定义多个值分配给不同的占位符,转义或跳过行等。 关于这些内容的摘录是您可以阅读文档的摘录,您可以阅读最新且完整的内容https://github.com/verhas/javageci/blob/master/ITERATE.adoc
文档摘录
在要使用生成器的Java源文件中,必须使用注释@Geci("iterate")
来注释该类。
您也可以改用@Iterate
批注,该批注在
javageci-core-annotations
模块。 这将指示Geci框架您要在给定类中使用iterate
生成器。
TEMPLATE
模板在/\*TEMPLATE
或TEMPLATE
行之后开始。
/*
和单词之间前后可以有空格
TEMPLATE
但线上不应有其他任何东西。 当生成器看到这样的行时,它将开始收集以下行作为模板的内容。
模板的末尾由一行上带有*/
信号线表示,没有其他内容(空格除外)。
模板的内容可以包含{{
和}}
之间的参数
字符,与胡子模板程序使用的字符类似。
(生成器不使用胡须,模板处理更为简单。)
LOOP
在收集模板的行时,某些行被识别为模板的参数定义。 这些行不会进入模板的主干。 (这些行上的命令名称始终为大写。)
如您在简介中所见
LOOP type =int|long|short
不是模板文本的一部分。 它通过类型指示发生器迭代并设置参数{{type}}
在文本到int
第一, long
第二和short
的最后。 这样,您可以遍历单个参数的多个值。
更复杂的模板可能需要多个参数。 在这种情况下,您可以将它们在LOOP
行中列出为
LOOP type ,var=int,aInt|long,aLong|short,aShort
这将告诉生成器将参数设置{{type}}
相同的方式,前三个迭代,但同时也设置了参数{{var}}
到aInt
在第一循环中,以aLong
在第二循环中和最后一个循环中的aShort
。
如果值列表太长,则可以将列表分成多条LOOP
行。 但是,在这种情况下,必须在第二行,第三行等等在LOOP
行中重复这些变量。
它们的顺序可能有所不同,但是如果某些LOOP
行中存在未定义的变量,则将解析引用该变量的占位符并将其保留为{{placeholder}}
形式。
上面的例子也可以写成
LOOP type ,var=int,aInt
LOOP var, type =aLong,long
LOOP type ,var=short,aShort
并得出与上述LOOP
相同的值:
LOOP type ,var=int,aInt|long,aLong|short,aShort
默认
从文件的开头到结尾处理模板,并按此顺序准备生成的代码。
生成的代码的内容将直接插入模板editor-fold
的editor-fold
部分。 虽然这样的id
的
editor-fold
段并不是很有趣,您必须为每个段指定一个唯一的id
。 这是Java :: Geci框架的限制。
进阶使用
EDITOR-FOLD-ID
您可能会遇到多个模板,这些模板遍历不同的值,并且您希望结果进入相同的editor-fold
分割。 可以使用EDITOR_FOLD_ID
。
在下面的例子中
package javax0.geci.iterate.sutclasses; public class IterateOverMultipleValues {
/* TEMPLATE
{{ type }} get_{{ type }}Value(){
{{ type }} {{variable}} = 0;
return {{variable}};
}
LOOP type ,variable=int,i|long,l|short,s
EDITOR-FOLD-ID getters
*/
//
// nothing gets here
//
//
int get_intValue(){
int i = 0;
return i;
}
long get_longValue(){
long l = 0;
return l;
}
short get_shortValue(){
short s = 0;
return s;
}
// }
生成的代码进入具有id
名称的editor-fold
getters
即使这不是遵循模板定义的getters
。
使用此功能可以将生成的代码从多个迭代模板发送到单个段中。 通常,将模板和段保持在一起是一个好习惯。
ESCAPE
和SKIP
模板的结尾由*/
。 这实质上是评论的结尾。 如果要在模板中包含注释(如JavaDoc),会发生什么情况。 您可以在注释行的末尾添加*/
字符,但其中仍包含一些字符。 该解决方案不是很好,它实际上是一种解决方法。
要有一条完全是注释结尾的行,或者是模板处理可以解释的任何行,例如LOOP
行,您应该在前一行中包含除ESCAPE
任何行。 这将告诉模板处理将下一行包括在模板文本中,并在此后的行继续进行正常处理。
同样,您可以使用SKIP
行来完全忽略下一行。
使用这两个命令,您可以将任何内容包括到模板中。
一个示例显示如何将JavaDoc注释包括到模板中:
package javax0.geci.iterate.sutclasses; public class SkippedLines {
/* TEMPLATE
/**
* A simple zero getter serving as a test example
* @ return zero in the type {{ type }}
ESCAPE
*/
// SKIP
/*
{{ type }} get_{{ type }}Value(){
{{ type }} {{variable}} = 0;
return {{variable}};
}
LOOP type ,variable=int,i|long,l|short,s
EDITOR-FOLD-ID getters
*/
//
/**
* A simple zero getter serving as a test example
* @ return zero in the type int
*/
int get_intValue(){
int i = 0;
return i;
}
/**
* A simple zero getter serving as a test example
* @ return zero in the type long
*/
long get_longValue(){
long l = 0;
return l;
}
/**
* A simple zero getter serving as a test example
* @ return zero in the type short
*/
short get_shortValue(){
short s = 0;
return s;
}
// }
模板以注释开头,并且注释实际上可以包含任何其他开头的注释。 Java注释不嵌套。 模板的末尾是包含*/
字符串的行。 我们希望该行成为模板的一部分,因此我们在该行之前
ESCAPE
因此它不会被解释为模板的末尾。 另一方面,对于Java,这将结束注释。 要继续模板,我们必须“返回”注释模式,因为我们不希望Java编译器将模板作为代码来处理。 (最后但并非最不重要的一点是,因为使用占位符的模板可能不是语法上正确的Java代码片段。)我们需要一个新的/*
行,我们不想将其插入模板。
因此,此行之前包含// SKIP
的行。 (跳过行在命令前可以具有//
。)
您可以在生成的代码中看到的结果。 所有方法都具有正确的JavaDoc文档。
SEP1
和SEP2
遍历值已与占位符的名字分开,
并且|
值列表。 例如,上面的示例包含
LOOP type ,variable=int,i|long,l|short,s
两个占位符名称type
和variable
,每个占位符三个值。
占位符不需要包含特殊字符,如果它们是标准标识符,则最好。 但是,这些值可能包含逗号或竖线。 在这种情况下,你可以重新定义字符串(不仅是单个字符),模板LOOP
命令可以改用单一字符串,
和|
。
例如线
SEP1 /
表示名称和值应以/
分隔,而不是一个和
SEP2 &
的值的列表应当由一个字符分隔&
串。 SEP1
和SEP2
仅在它们之前
LOOP
命令,它们仅对所使用的模板有效。遵循上述命令, LOOP
示例将如下所示:
LOOP type /variable =int /i &long /l &short /s
这样,没有什么可以阻止我们添加另一个值列表
LOOP type /variable =int /i &long /l &short /s &byte,int /z
最终将导致示例模板出现语法错误,但演示了重新定义名称和值列表分隔符的要点。
组态
生成器由Geci框架支持的配置工具实现,并且所有参数都是可配置的。 您可以在类的注释中或在编辑器折叠参数中,重新定义与模板的开始,结束,跳过等行相匹配的正则表达式,在创建生成器对象的单元测试中。
带走
迭代生成器是一种非常易于使用的生成器,用于创建重复的代码。 这也是主要的危险:您应该足够强大才能找到更好的解决方案,并仅在最佳解决方案时才使用它。
翻译自: https://www.javacodegeeks.com/2019/11/repeated-code.html
所有评论(0)