从此代码开始:
new Person("ET").WithAge(88)
如何将其重构为:
new Person("ET", 88)
需要执行哪些重构序列才能完成转换?
为什么?因为可能有数百个,我不想通过手动操作来引入错误。
你会说流畅接口的一个缺点是它们不容易被重构吗?
注意:我想自动执行此操作而无需手动输入代码。
从此代码开始:
new Person("ET").WithAge(88)
如何将其重构为:
new Person("ET", 88)
需要执行哪些重构序列才能完成转换?
为什么?因为可能有数百个,我不想通过手动操作来引入错误。
你会说流畅接口的一个缺点是它们不容易被重构吗?
注意:我想自动执行此操作而无需手动输入代码。
也许最简单的重构方法是将名称“WithAge”更改为“InitAge”,设为InitAge
私有,然后从您的构造函数中调用它。然后更新所有引用new Person(string).WithAge(int)
以使用新的构造函数。
如果WithAge
是单行代码,则可以将代码移至新的构造函数,并InitAge
完全取消,除非使用附加方法提供额外的可读性。
拥有良好的单元测试将隔离引入错误的位置(如果有的话)。
假设 WithAge 是一个返回 Person 的 Person 方法,那么类似
Person(string name, int age)
{
this.name = name;
this.WithAge(age);
}
或更笼统地说:
Person(SomeType originalParameter, FluentParamType fluentParameter)
{
//Original constructor stuff
this.FluentMethod(fluentParameter);
}
然后,如果您不想要 FluentMethod,则将其设为私有,或者如果您想同时允许两种方式,则将其公开。
如果这是 C#(理想情况下你会用语言标记问题), Person 类需要这个构造函数:
public Person(string name, int age)
: this(name) { WithAge(age); }
然后要更改所有客户端代码以在适当的情况下调用此新构造函数,您需要找到所有出现的模式:
new Person(x1).WithAge(x2)
其中 x1 和 x2 是表达式,并将它们替换为:
new Person(x1, x2)
如果除了 WithAge 之外还有其他修饰符方法,它可能会变得更加复杂。例如:
new Person(x1).WithHair(x2).WithAge(x3)
也许你希望它变成:
new Person(x1, x3).WithHair(x2)
这完全取决于您是否有一个 IDE,可以让您定义类似的语言感知搜索/替换模式。通过简单的文本搜索和替换,结合重放一系列按键的宏,您可以获得很长的解决方案。
你会说流畅接口的一个缺点是它们不容易被重构吗?
并非特别 - IDE 中的重构功能要么设计得足够灵活,让您创造性地发明新的重构,要么针对某些常见情况进行硬编码。我更愿意将常见案例定义为我可以变异以发明新案例的示例。
我在这类事情上没有任何实际经验,但如果我遇到你的情况,我会去寻找自定义 Eclipse 重构(或 Refactor 中的等价物!Pro for .Net,如果你是这样的话使用)。
基本上你想要的是匹配和替换,除了你的正则表达式应该匹配抽象语法树而不是纯文本。这就是自动化重构。
这种重构的一个风险是目标版本不如原始版本精确。考虑:
类人{ 公共人员(字符串名称,整数年龄); 公共人员(字符串名称,int numberOfChildren); }
无法判断对 Person.WithAge 的链式调用应该替换为哪些构造函数。
因此,对此的自动支持必须在允许您继续之前检查此类歧义。如果已经存在带有目标参数的构造函数,则中止重构。
除此之外,它似乎很简单。给新的构造函数以下内容:
公共人员(字符串名称,整数年龄){ 这个(名字); 与年龄(年龄); }
然后,您可以安全地用新调用替换原来的调用。
(还有一个微妙的额外风险,因为在构造函数中调用 withAge ,即在部分构造的对象上,与在构造函数之后调用它并不完全相同。如果你有一个继承链并且如果 withAge 做了一些事情,差异很重要不平凡。但这就是你的单元测试的目的......)
为旧代码编写单元测试。
重构直到测试再次通过。