发布时间:2025-05-18
浏览次数:0
前次我所分享的,是一些关于如何运用基础的简单功能来完成复杂重构需求的技巧。观察了大家的反馈后,我觉得那可能并不太易于理解,因为许多人甚至都不了解这项功能(难道他们只是将工具当作了记事本来使用),因此我决定再撰写一篇,来介绍一些工具已经内置的更为复杂的重构功能。
这不再是一本需要读者自行进行古怪操作的指南,它将变得更加贴近大众。
从方法中提取方法
这一功能旨在高效地重复使用代码片段,它被称作“”。以我目前使用的这段业务代码为例——顺便提及,这是在Java中调用动态语言API时最为稳固处理数值类型数据的方法之一:,
liceEnv.defineFunction("run-later", ((metaData, nodes) -> {
Number time = (Number) nodes.get(0).eval();
Consumer<Node> nodeConsumer = Node::eval;
if (time != null) runLater(time.longValue(), () -> {
for (int i = 1; i < nodes.size(); i++) {
// 截图之前写的时候脑抽了,这个是后来改的
nodeConsumer.accept(nodes.get(i));
}
});
return new ValueNode(null, metaData);
}));
...
鉴于效率的考量,你选择了放弃调用(1,nodes.size())这一方法,转而采用for循环来进行操作。
接着你猛然意识到,该程序中“对集合中除首个元素外所有元素进行遍历”的操作被频繁执行。因此,你采纳了“非极端重复代码(DRY)原则”,打算将这段代码进行复用。
我们仔细观察一下。这坨代码中,直觉上,我们希望可以通过形如
nodes.forEachExceptFirst(someOperation::accept)
此操作无法通过单行代码实现,若不懂得方法引用intellij idea,请自觉退群,因为这样的代码根本不存在。
因此,我们打算自行打造一个。在这种情况下,我们应当启用IDEA所提供的各项功能。
在“名称”这一栏填写,即我们要检索的函数的名称;接着按下回车键。
我们可以看到,代码变成了这样:
liceEnv.defineFunction("run-later", ((metaData, nodes) -> {
Number time = (Number) nodes.get(0).eval();
Consumer<Node> nodeConsumer = Node::eval;
if (time != null) runLater(time.longValue(), () -> {
forEachExceptFirst(nodes, nodeConsumer);
});
return new ValueNode(null, metaData);
}));
...
我们可以看看它生成的这个 方法:
private void forEachExceptFirst(
List extends Node> nodes,
Consumer<Node> nodeConsumer) {
for (int i = 1; i < nodes.size(); i++) {
nodeConsumer.accept(nodes.get(i));
}
}
然后你就可以在其他地方使用这个方法了。
我们可以给它加上 :
private void forEachExceptFirst(
@NotNull List<@NotNull ? extends @NotNull Node> nodes,
@NotNull Consumer<@NotNull Node> nodeConsumer) {
for (int i = 1; i < nodes.size(); i++) {
nodeConsumer.accept(nodes.get(i));
}
}
当然,增加这些内容意义并不显著,对于 Node 类型的那个特殊标记符号,其实是可以省略的。
撤回这个操作的话,请使用上一篇博客所大量使用的 功能。
从类中提取接口
例如,我们这里有一个Java类,近期我有个新想法,认为类型注解理应比可见性修饰符更为紧密地关联(比如,在方法内部,我就能通过这种方式来区分出对返回类型的注解,如@,以及针对方法自身的注解,如@)。因此,就出现了将注解放置在可见性修饰符之后的这种不太寻常的书写方式,在此希望读者能够理解并予以谅解。
public class Marisa {
// blablabla
public Marisa(@NotNull Touhou game) {
// blablabla
}
public @NotNull ImageObject player() {
return player;
}
public @NotNull List<@NotNull ImageObject> bullets() {
return makeLeftRightBullets(player, mainBullet);
}
public void dealWithBullet(@NotNull ImageObject bullet) {
// blablabla
}
}
代码中省去了一些对文章不重要的细节。
然后我们可以在类名上右键,然后找到这个东西:
这样我们会看到一个窗口,里面的东西还挺复杂的:
我们在“name”这一栏输入我们希望选取的接口名称,例如,之前提到的那个类,它非常适合(因为魔理沙是幻想乡中的两位城管之一,而且城管的翻译是)这样的接口名字。
我们期望将这三种技术手段整合至接口之中,因此对下方的三项功能进行了选择。需依据具体需求来挑选相应的功能。完成选择后,请按回车键。
此刻,IDEA 将会向你提问,是否“在尽可能的范围内,将当前类中的类型替换为接口的类型”。
这是一种很好的作法,比如我们会倾向于把
LinkedList<Marisa> gensokyoManagements = new LinkedList<Marisa>();
写成
List<GensokyoManagement> gensokyoManagements = new LinkedList<Marisa>();
这个提示是在询问你是否需要做出这样的调整。这取决于你的具体需求。此外,我建议你取消对下方的“to be”选项的选择。
最终,我们成功提取出了这样的成果(鉴于此处仅涉及三种方法,生成的代码量并不多,因而看上去不够炫酷,但若你针对操作频繁的数据结构(例如线段树、各式各样的图和树等)再次采用此法,便能产出大量的代码块):
public interface GensokyoManagement {
@NotNull ImageObject player();
@NotNull List<@NotNull ImageObject> bullets();
void dealWithBullet(@NotNull ImageObject bullet);
}
然后我们就可以再写其他类,比如:
public class Reimu implements GensokyoManagement {
}
接下来,让IDEA自动构建出之前所用的那些方法,随后我们便可以轻松地着手编写具体实现了。
接口与实现间的互相发送代码
我们尚有许多工作待完成,例如intellij idea,我们已为类引入了新的方法以增添新功能;接下来,我们计划为Reimu也添加此类方法,并将此方法定位为的一个抽象方法之一——需注意,接口中的方法是默认抽象的,切莫因省略了修饰符而误以为其非抽象。
public @NotNull List<@NotNull ImageObject> spellCard() {
return masterSpark();
}
我们可以这样,在新方法上右键,然后这么选:
这样我们会看到一个窗口,里面的东西不怎么复杂:
只需挑选出我们打算赋予接口(或父类)的功能方法,紧接着按下回车键即可。IDEA便会自动为所选方法添加@修饰符,并创建相应的抽象方法。
随后,我们将转向 Reimu 类,借助 IDEA 创建一个初始的空实现,之后便可以开始编写代码了。
如有侵权请联系删除!
Copyright © 2023 江苏优软数字科技有限公司 All Rights Reserved.正版sublime text、Codejock、IntelliJ IDEA、sketch、Mestrenova、DNAstar服务提供商
13262879759
微信二维码