业务规则——它们在 OOP 中去哪里?
我有一堂课:时间表。
public class Schedule {
private int locationNum;
private int cost;
private String costReason;
private Date weekOfChange;
private Date dayOfChange;
private String changeReason;
// and all those getters and setters
public Schedule(int locationNum, int cost, String costReason, Date weekOfChange, Date dayOfChange, String changeReason) throws ApplicationException {
//change is all or nothing - all attributes are present or none
if((weekOfChange!=null && dayOfChange!=null && changeReason!=null) || (weekOfChange==null && dayOfChange == null && changeReason == null)) {
this.weekOfChange = weekOfChange;
this.dayOfChange = dayOfChange;
this.changeReason = changeReason;
}
else { throw new ApplicationException();}
//similary another if block to ensure that if cost is specified
//then there exists the corresponding reason code for it.
}
}
到目前为止,我很喜欢我的时间表课程。 但是,我还没有完成检查,我还必须做一些其他检查:
- locationNum 是否是数据库中的有效商店编号。
- ChangeReason 文本是数据库中 6 个不同的 ChangeReason 代码之一。
- 等等......
通常,我不会在 Schedule 类中编写这些,因为显然,我无法从此类调用 DAO。 因此,我将有一个业务层和某种验证器类,接受 Schedule 类型的对象并按顺序执行一堆数据库验证并收集错误以进行显示/其他操作。
现在,我的问题是:
- 如果您将 Schedule 视为 POJO 并认为对象不负责验证自身 - 我必须将构造函数中的所有代码移至业务层的验证器类。 但是,如果我这样做的话,日程安排不是很贫乏吗? 这就是他们所说的违反单一责任原则吗?
- 假设我将构造函数中的代码移至业务层类,以便所有类型的验证现在都位于我的业务层中。 假设有人将我的数据存储中的 dayOfChange 更改为 NULL,现在我正在从数据库加载对象。 现在,有了这种对象,我的应用程序可能会崩溃,不是吗? 因为我会假设满足验证规则来编写代码。 我想我的问题变得令人困惑,但我想要指出的是,在这种情况下,我宁愿在构造函数中完成这些检查,以确保 Schedule 类隐藏的数据的完整性。
- 通常是如何完成的? 最佳实践是什么?
感谢您参与本次讨论。
I have a class : Schedule.
public class Schedule {
private int locationNum;
private int cost;
private String costReason;
private Date weekOfChange;
private Date dayOfChange;
private String changeReason;
// and all those getters and setters
public Schedule(int locationNum, int cost, String costReason, Date weekOfChange, Date dayOfChange, String changeReason) throws ApplicationException {
//change is all or nothing - all attributes are present or none
if((weekOfChange!=null && dayOfChange!=null && changeReason!=null) || (weekOfChange==null && dayOfChange == null && changeReason == null)) {
this.weekOfChange = weekOfChange;
this.dayOfChange = dayOfChange;
this.changeReason = changeReason;
}
else { throw new ApplicationException();}
//similary another if block to ensure that if cost is specified
//then there exists the corresponding reason code for it.
}
}
So far, I am liking my Schedule class. However, I am not done with the checks, I would have to do some other checks:
- is the locationNum a valid store number in the database.
- is changeReason text one of those 6 different changeReason codes in the database.
- etc etc....
Typically, I would not write these in Schedule class, coz obviously, I cannot invoke DAOs from this class. So, I would have a business layer and some kind of validator class, accepting an object of type Schedule and performing a bunch of database validations in sequence and collecting errors for display / whatever.
Now, here are my questions:
- If you were to treat Schedule as a POJO and argue that an object is not responsible for validating itself - I would have to move all of the code in the constructor to the business layer's validator class. But, if I were to do this, isn't Schedule anemic? Is this what they call violation of Single Responsibility principle?
- Lets presume I moved the code I have in my constructor to a business layer class so that all kinds of validations are now in my busines layer. Lets assume that someone changes dayOfChange to NULL in my datastore and now I am loading objects from my database. Now, with this kind of an object, my application can break, wouldn't it? coz I would have written code assuming that validation rules are met. I guess my question is getting confusing, but the point I am trying to make is that in this context I would rather have these checks done in constructor to ensure integrity of the data hidden by Schedule class.
- How is it done usually? What is the best practice?
Appreciate your involvement in this discussion.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
如果您确实拥有“所有这些 getter 和 setter”,那么您需要比在构造函数中更好地验证您的类。 如果类的不变量是所有 weekOfChange、dayOfChange 和 changeReason 都必须为 null 或并非全部都必须为 null,那么您的 setter 很快就会将您的类置于无效状态。 你的意思是有设置器还是你的类是不可变的?
在担心验证之前,也许应该对您的班级进行分析。 查看给定状态的所有有效状态和不变量。 然后你就会明白你的类应该是可变的还是不可变的。 如果您有相互依赖的协变(例如 weekOfChange、dayOfChannge 和 changeReason),则将它们打包到自己的类中并在 Schedule 类中使用组合是有意义的。这会将有关这些事物字段的“规则”放在一个位置并简化 Schedule 与其他协作字段(例如成本和成本原因)相同
您自己就有一个更容易的域。
,那么 Schedule 由自我验证类组成,如果这两个字段都是不可变的,那么 :最好是类定义其状态和不变量,并在可行的情况下仅向其协作者公开最少的状态和不变量。 Schedule 的内部状态的责任应由 Schedule 承担,并且只需进行更多设计即可相对轻松地完成
。 您也有
类似的合作者。
尽管我强烈建议您通过对客户端代码传入的任何可变值进行防御性复制来保护您的类不变量,但
If you really have "all those getters and setters" then you need to validate your class better than in the constructor. If the invariants of your class are such that all weekOfChange, dayOfChange and changeReason must be null or not all must be not null your setters are quickly going to put your class in an invalid state. Do you mean to have setters or do you mean your class to be immutable?
Before worrying about were validation should go maybe do an analysis of your class. Look at all its valid states and invariants for given states. Then you will understand whether your class should be mutable or immutable. Where you have mutually dependant covariants (like weekOfChange, dayOfChannge and changeReason it makes sense to package them into there own class and use composition in the Schedule class. This will put the "rules" about these things fields in a single place and simplify the Schedule.
The same with other collaborating fields (like cost and cost reason). Then Schedule is composed of to self validating classes. If both these are immutable and Schedule is immutable also you have your self a much easier domain.
So, to answer your question: it is better that a class defines its states and invariants and exposes only the minimum to its collaborators where practical. The responsibility for the internal state of Schedule should rest with Schedule and with just a little more design it can do with relative ease.
So you have
And colloborators like
Though I would strongly advise you protect your classes invariants with defensive copying of any mutable values that are passed in by client code.
POJO 仅意味着您不必从特定类派生或使用字节码增强器以获得所需的功能,例如 OR 映射。 这并不意味着这些对象应该是缺乏功能的。
谁说您不能从 Schedule 类的构造函数中调用在单独的类中定义的规则? 如果它需要访问非公共字段,您可以将它们设为包私有并将规则类放在同一包中 - 或者将它们作为参数传递给规则。
是否将验证规则移至单独的类应取决于应用程序的需求,而不是取决于被误解的流行语。
拥有单独的规则类的好理由是:
POJO only means that you don't have to derive from a particular class or use a byte code enhancer to get desired functionality like OR mapping. It does NOT mean that the objects should be anemic with no functionality.
Who says you cannot call rules defined in a separate class from the constructor of your Schedule class? If it needs access to non-public fields, you could make them package-private and have the rules class in the same package - or pass them as parameters to the rule.
Whether or not you move validation rules to separate classes should depend on your application's needs, not on misunderstood buzzwords.
Good reasons to have separate rule classes would be:
看起来
Schedule
构造函数有两种不同的行为。 因此,应该有两个构造函数。 甚至可能有两个实现类。locationNum
和changeReason
。 这些目前是弱类型的。 他们应该有自己的班级。 根据您的问题,有效值取决于上下文。 目前Schedule
没有任何上下文。 您可以使Schedule
独立于上下文,在这种情况下,locationNum
和changeReason
上没有数据库约束。 另一方面,Schedule
、locationNum
和changeReason
可能依赖于上下文,并且构造函数应该仅检查它们是否都在同一位置语境。 包私有构造函数(充当穷人的朋友
)可能会省略检查。POJO 意味着一个(写得好的)对象,因此意味着行为。 问题似乎是用它来表示“结构”。 避免结构。
业务层似乎有误导性。 它看起来是一个商业实体,它应该有商业行为。 但是,它可能不包含业务流程(服务)。
您的应用程序将始终假设有关数据库的某些事情,包括(公共)模式。 如果您删除限制,那么您很可能会破坏您的应用程序。 这与您类似地更改所依赖的代码是一样的。
常见的做法是使用带有程序代码的贫血对象。 对于大多数情况,这不是最佳实践。
It looks like there is two distinct behaviours for the
Schedule
constructor. There, therefore, should be two constructors. Possibly even two implementation classes.locationNum
andchangeReason
. These are currently weakly typed. They should probably have their own class. Following your question, the valid values depend upon the context. At the momentSchedule
doesn't have any context. You could keepSchedule
independent of context, in which case there are no database constraints onlocationNum
andchangeReason
. Going the other way,Schedule
,locationNum
andchangeReason
could be dependent on context, and the constructor should merely check that they are all in the same context. A package-private constructor (acting as a poor man'sfriend
) may omit the checks.A POJO implies a (well written) object and therefore behaviour. The question seems to be using it to mean "struct". Avoid structs.
Business layer seems to be misleading. It appears to be a business entity, it should have business behaviour. However, it may not contain business process (service).
Your application will always assume certain things about the database, including the (public) schema. If you remove constraints, then you may well break your application. This is the same as if you similarly changed code that is depended upon.
The common practice is to have anemic objects with procedural code. For most cases, this is not best practice.
为什么 locationNum 是 int,而实际上您引用的是另一个类(例如 Location)的实例? 如果您使用正确的对象引用,您可以在 Schedule 中验证它们并配置 ORM(如果有)以强制引用完整性。
Why is locationNum an int when in fact you refer to an instance of another class, say Location? If you'd use proper object references you could validate them in Schedule and configure the ORM (if any) to enforce referential integrity.
回答你的问题:
1)我不认为你的班级会贫血,暂时会成为DTO,我认为这没有问题。
2)如果你的验证是在业务层,这并不意味着你会得到一个无效的对象,因为验证仍然存在,通常你的验证过程会以某种方式抱怨 InvalidDataException 或其他东西并且不允许您使用“损坏的”对象。 如果 null 日期不是可接受的值( null 通常无法进入数据库),那么业务/dao 层应该捕获此错误并阻止该对象被您的应用程序使用。
to answer your questions :
1) I don't think your class will be anemic, it will rather be a DTO for the moment, I see no problem with this.
2) If your validation is in the business layer, it doesn't mean you'll be getting an invalid object, because the validation will still be there, normally your validation process will somehow complain about an InvalidDataException or something and won't allow you to use the "corrupted" object. If the null date is not an acceptable value ( null could not normally get into the database ), then the business/dao layer should catch this error and prevent the object to get used by your app.