我们正在重构一些代码。我们在一个项目中开发了一个功能,我们希望在其他项目中使用这个功能。我们正在提取此功能的基础,并使其成为一个完整的项目,然后可以由其当前项目和其他项目导入。这项工作相对简单,但我们头疼不已。
当最初开发相关框架时,我们选择将各种常量值定义为单个类中的静态字段。随着时间的推移,这些静态成员列表增长该类在我们的代码中的很多地方使用。在我们目前的重构中,我们将把这个类的一些成员提升到我们的新框架,但留下其他成员。我们头疼的是提取要在我们的新项目中使用的此类的基础成员,更具体地说,我们应如何在现有代码中解决这些提取的成员。
我们知道我们可以将现有的Constants类子类化为这个新项目的Constants类,并且它将继承所有父项的静态成员。这将允许我们在不触及使用这些成员更改静态引用上的类名的代码的情况下实现更改。然而,这种选择中固有的紧密耦合感觉不对。
之前:
public class ConstantsA {
public static final String CONSTANT1 = "constant.1";
public static final String CONSTANT2 = "constant.2";
public static final String CONSTANT3 = "constant.3";
}
后:
public class ConstantsA extends ConstantsB {
public static final String CONSTANT1 = "constant.1";
}
public class ConstantsB {
public static final String CONSTANT2 = "constant.2";
public static final String CONSTANT3 = "constant.3";
}
在我们现有的代码分支中,可以通过以下方式访问上述所有内容:
ConstantsA.CONSTANT2
我想征求关于这是否“可接受”和/或最佳做法的争论。
答案 0 :(得分:9)
只有静态字段的类才是代码气味。这不是一个班级。
有些人使用接口,因此他们可以实现它以更容易地使用常量。但是接口应该仅用于模拟类的行为。 (http://pmd.sourceforge.net/rules/design.html#AvoidConstantsInterface)使用Java 5中的静态导入根本不需要简单的常量使用。
您的常量是否真的是字符串,或者只是用作字符串。如果它们是某些类型的不同选项(所谓的枚举),则应使用typesafe enumerations,使用Java 5中的枚举或Commons Lang提供的枚举。当然,将代码转换为使用枚举可能有点小问题。
您至少应该将常量拆分为具有正确商家名称的文件中的相关常量组。在IDE中移动最终成员很容易,并且会更新所有用法。
如果你负担得起,那么将它们转换为枚举。 (考虑使用脚本来执行此操作,通常是可能的。)如果常量/枚举之间存在关系,则类层次结构仅有用。如果必须,你可以保留字符串,但仍然将它们视为实体,然后扩展可能对某些人有意义(描述是一种关系)。如果序列化不是问题,那么第一个枚举可以是由您自己制作的简单类。由于其类型安全性以及显示打算或业务/域特定事物的额外名称,枚举始终是有利的。
如果常量确实是字符串常量,请使用Properies或ResourceBundle,可以使用纯文本文件进行配置。您可以再次使用常量名称作为资源包密钥编写重构脚本,并自动生成这两个文件。
答案 1 :(得分:2)
我不喜欢它,但它可能是你现在能做的最好的。
正确的答案是将常量分解为连贯的组,随着时间的推移修复代码中断。在C#中,我使用枚举。
答案 2 :(得分:2)
Peter Kofler已经讨论了如何更好地组织常数。我将分享如何自动化转换:
日食“内联”重构可以通过定义自动替换常量,从而使您不必追捕并手动更改每个用法。所以你只需将代码更改为:
public class ConstantsA {
public static final String CONSTANT1 = "constant.1";
public static final String CONSTANT2 = ConstantsB.CONSTANTFOO;
public static final String CONSTANT3 = ConstantsB.CONSTANTBAR;
}
public class ConstantsB {
public static final String CONSTANTFOO = "constant.2";
public static final String CONSTANTBAR = "constant.3";
}
...然后让eclipse内联COONSTANT2和CONSTANT3(当所有受影响的项目都被检出时,如果你不能这样做,请查看重构脚本),然后你就完成了。
答案 3 :(得分:0)
我已经通过在接口上放置静态最终String来完成此操作,因此您可以“实现”它,而不必担心在需要不同的基类时该怎么做。它就是这样的。
总的来说,enums相当擅长你想要做的事情,并且可能会摆脱“我不确定”你正在经历的感觉,因为这是枚举的意图。
答案 4 :(得分:0)
我认为你做得很好。是的,这些类是紧密耦合的,但这是重点 - 您希望能够仅引用一个类来查看所有项目范围的常量。
您必须努力确保ConstantsB仅包含可在所有项目中推广的常量,并且ConstantsA仅包含项目特定的常量。如果,稍后你意识到ConstantsB中有一个常量,你似乎在你的子类中占了很多,那么这表明它应该从来没有放在ConstantsB中。
答案 5 :(得分:0)
我认为你所拥有的是迈出的第一步。下一步是逐步将所有对ConstantsA.CONSTANT2
和ConstantsA.CONSTANT3
的引用替换为ConstantsB.CONSTANT2
和ConstantsB.CONSTANT3
,直到您删除extends
。
如果通过子类引用超类常量,大多数IDE都可以配置为显示警告,我猜想像FindBugs这样的静态分析工具也可以这样做。
一个可能稍微清洁的想法:
ConstantsA
并将其称为LegacyConstants
LegacyConstants
扩展所有其他模块化Constants
接口LegacyConstants
目标是在Constants
接口之间不进行任何继承。 LegacyConstants
将是唯一存在任何继承的地方,它不会声明它自己的任何常量,并且当它不再使用时 - 当使用它的每个类代替正确的Constants
时界面 - 你已经完成了重构。
答案 6 :(得分:0)
提取常量时,让旧类引用新类中定义的常量。这里真的没有必要创建继承关系。
答案 7 :(得分:0)
我可能错了,但我认为我们根本不需要常数。它只是意味着你不能改变常量的值,你可能应该这样做。