我目前正在设计一个API,我希望通过各种方法进行配置。一种方法是通过XML配置模式,另一种方法是通过我希望与Spring很好地协作的API。
我的XML架构解析代码以前是隐藏的,因此唯一关心的是它可以工作但现在我希望构建一个公共API,我非常关注最佳实践。
似乎很多人赞成使用默认零参数构造函数的javabean类型PoJo,然后使用setter注入。我试图解决的问题是,某些setter方法实现依赖于在它们之前按顺序调用的其他setter方法。
我可以编写肛门设置器,它可以容忍自己在许多命令中被调用但是这不能解决用户忘记设置适当的setter并因此bean处于不完整状态的问题。
我能想到的唯一解决方案是忘记对象 'beans'并通过构造函数注入强制执行所需的参数。
这方面的一个例子是基于父组件的id的组件id的默认设置。
我的界面
public interface IMyIdentityInterface {
public String getId();
/* A null value should create a unique meaningful default */
public void setId(String id);
public IMyIdentityInterface getParent();
public void setParent(IMyIdentityInterface parent);
}
接口的基本实现:
public abstract class MyIdentityBaseClass implements IMyIdentityInterface {
private String _id;
private IMyIdentityInterface _parent;
public MyIdentityBaseClass () {}
@Override
public String getId() {
return _id;
}
/**
* If the id is null, then use the id of the parent component
* appended with a lower-cased simple name of the current impl
* class along with a counter suffix to enforce uniqueness
*/
@Override
public void setId(String id) {
if (id == null) {
IMyIdentityInterface parent = getParent();
if (parent == null) {
// this may be the top level component or it may be that
// the user called setId() before setParent(..)
} else {
_id = Helpers.makeIdFromParent(parent,getClass());
}
} else {
_id = id;
}
}
@Override
public IMyIdentityInterface getParent() {
return _parent;
}
@Override
public void setParent(IMyIdentityInterface parent) {
_parent = parent;
}
}
除顶层组件外,框架中的每个组件都有一个父组件。使用setter类型的注入,然后setter将根据setter的调用顺序具有不同的行为。
在这种情况下,您是否同意,引用父项的构造函数更好并完全从接口中删除父setter方法?如果我希望能够使用IoC容器配置这些组件,这被认为是不好的做法吗?
克里斯
答案 0 :(得分:2)
为此目的使用IOC容器并不是一个坏习惯。使用contrucxtor或setter注入的选择是由这样一个事实驱动的,即是否应该在没有父类的情况下创建这些类的任何实例。因为在你的情况下,父总是需要设置(除了顶部节点)构造函数注入很有意义
答案 1 :(得分:2)
我真的不认为问题是setter与构造函数注入。问题是你使用null
来表示太多事情......
至于:/* A null value should create a unique meaningful default */
在setId()
方法中使用它作为“魔法”值也是一种真正看起来像是一种糟糕的代码味道。如果您从未致电setId()
,则getId()
会返回null
,但如果您致电setId(null)
,那么getId()
会返回一些生成的值?
这样的事情似乎更有意义:
public abstract class MyIdentityBaseClass implements IMyIdentityInterface {
private String _id;
private IMyIdentityInterface _parent;
public MyIdentityBaseClass () {}
@Override
public String getId() {
return _id;
}
@Override
public void setId(String id) {
_id = id;
}
@Override
public IMyIdentityInterface getParent() {
return _parent;
}
@Override
public void setParent(IMyIdentityInterface parent) {
_parent = parent;
if (_id == null) {
// if id isn't already set, set it to the generated id.
_id = Helpers.makeIdFromParent(parent, getClass());
}
}
}
如果你的模型真的需要父母随意使用构造函数注入,那就是它的用途。但是要意识到你仍然必须为顶级项目提供默认构造函数......然后阻止某人使用默认构造函数来处理不是顶级项目然后再调用setParent()
的项目。我将着眼于创建某种容器/组件体系结构,并为顶级项目创建一个特殊的类(或类)。 (你可能已经有了这个,因为你只给了一个示例类和一个抽象的类。)
BTW - 此外,IInterface和_name约定不是Java命名约定。如果这是一个暴露的API,其他人应该使用,期望一些抱怨。
答案 2 :(得分:1)
我可以编写肛门设置器,它可以容忍自己在许多命令中被调用但是这不能解决用户忘记设置适当的setter并因此bean处于不完整状态的问题。
特定于Spring的解决方案是将bean声明为实现InitializingBean
,然后在bean的afterPropertiesSet()
方法中实现必要的检查和回填默认值。您甚至可以将其连接起来,以便程序员可以选择使用带有参数或setter的构造函数,并且(可能)让argument-full构造函数调用afterPropertiesSet()
。
唯一的陷阱是:
凭借实施InitializingBean
和
如果开发人员使用setter手动配置bean,他们必须记住在完成后调用afterPropertiesSet()
。
修改强>
afterPropertiesSet()
方法还为计算id的示例提供了一个简洁的解决方案。您当前编写它的方式的问题在于它隐式地限制您从顶部(根)向下构建树。例如,假设您有一个多级树,并在设置父节点的父属性之前设置叶节点的父属性。这将触发叶子节点中的id
设置算法,该算法将错误地认为父节点是根节点。
在Spring中,您可以安排parent
属性的设置是在父节点的相关setChild / setChildren方法的实现中完成的。然后,id
方法将设置afterPropertiesSet()
属性。由于在之后调用了,所有显式和自动连线属性设置都已应用,因此树的父/子关系将会稳定,因此id
计算将是正确的。< / p>
答案 3 :(得分:1)
听起来问题在于默认ID。如果bean本身不能确定其ID,它将使您的生活更加轻松。我会说ID是构造或使用bean的客户端代码的责任。
当然,我不知道确切的上下文,但我认为ID应该有点独特(在bean自己的上下文中)。您当前的解决方案表明ID设置在两个不同的位置:setId()
方法或客户端代码。
在一般情况下,拥有一个负责管理此类ID的地方要好得多。将ID设置为bean内部的默认值可能实际上隐藏了一些其他错误。
如果您确实认为未来的客户端代码可能无法正确使用您的bean是一个问题,那么我宁愿在getter或setter中验证bean的状态并抛出运行时异常,例如IllegalStateException
或IllegalArgumentException
。