3

我有一堂课:时间表。

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.
}
}

到目前为止,我喜欢我的 Schedule 课程。但是,我还没有完成检查,我将不得不做一些其他检查:

  • locationNum 是数据库中的有效商店编号。
  • 是数据库中这 6 个不同的 changeReason 代码之一的 changeReason 文本。
  • 等等等等……

通常,我不会在 Schedule 类中编写这些,因为很明显,我不能从此类中调用 DAO。因此,我将有一个业务层和某种验证器类,接受一个 Schedule 类型的对象并按顺序执行一堆数据库验证并收集错误以进行显示/其他。

现在,这是我的问题:

  1. 如果您将 Schedule 视为 POJO 并争辩说对象不负责验证自身 - 我将不得不将构造函数中的所有代码移动到业务层的验证器类。但是,如果我要这样做,难道不是日程安排乏力吗?这就是他们所说的违反单一责任原则吗?
  2. 假设我将构造函数中的代码移到业务层类中,以便现在所有类型的验证都在我的业务层中。假设有人在我的数据存储中将 dayOfChange 更改为 NULL,现在我正在从我的数据库中加载对象。现在,有了这种对象,我的应用程序可能会崩溃,不是吗?因为假设满足验证规则,我会编写代码。我想我的问题越来越令人困惑,但我想说的是,在这种情况下,我宁愿在构造函数中完成这些检查,以确保 Schedule 类隐藏的数据的完整性。
  3. 一般是怎么做的?最佳做法是什么?

感谢您参与本次讨论。

4

5 回答 5

5

如果您真的拥有“所有这些 gettersetter”,那么您需要比在构造函数中更好地验证您的类。如果你的类的不变量是这样的,所有的 weekOfChange、dayOfChange 和 changeReason 都必须为空,或者不是所有的都不能为空,那么你的设置器很快就会让你的类处于无效状态。你的意思是有二传手还是你的类是不可变的?

在担心验证之前应该对您的课程进行分析。查看给定状态的所有有效状态和不变量。然后你就会明白你的类应该是可变的还是不可变的。如果您有相互依赖的协变量(例如 weekOfChange、dayOfChannge 和 changeReason,将它们打包到自己的类中并在 Schedule 类中使用组合是有意义的。这会将有关这些事物字段的“规则”放在一个地方并简化 Schedule .

与其他协作领域相同(如成本和成本原因)。然后 Schedule 由自我验证类组成。如果这两个都是不可变的并且 Schedule 是不可变的,那么您的自己也将拥有一个更容易的域。

所以,回答你的问题:一个类最好定义它的状态和不变量,并在可行的情况下只向它的合作者公开最小值。Schedule 的内部状态应该由 Schedule 负责,并且只需多一点设计,它就可以相对轻松地完成。

所以你有了

    class Schedule {
         private Change change;
         private Cost cost;
         private Location location;

        public Schedule(Location location, Cost cost, Change change) {
           this.change = change;
           this.cost = cost;
           this.location = location;
        }
}

和合作者喜欢

public class Change {
    private Date weekOfChange; //shoudln't these two fields be one, a date is a date after all??
    private Date dayOfChange; //shoudln't these two fields be one??
    private String changeReason; 

    public Change(Date weekOfChange Date dayOfChange, String changeReason) {
      this.weekOfChange = weekOfChange;
      ...etc.
    } 
}

尽管我强烈建议您通过防御性复制客户端代码传入的任何可变值来保护您的类不变量。

于 2009-06-04T16:10:08.400 回答
4

如果您将 Schedule 视为 POJO 并争辩说对象不负责验证自身 - 我将不得不将构造函数中的所有代码移动到业务层的验证器类。但是,如果我要这样做,难道不是日程安排乏力吗?这就是他们所说的违反单一责任原则吗?

POJO仅意味着您不必从特定类派生或使用字节码增强器来获得所需的功能,例如 OR 映射。这并不意味着对象应该是贫血的,没有功能。

现在,有了这种对象,我的应用程序可能会崩溃,不是吗?因为假设满足验证规则,我会编写代码。我想我的问题越来越令人困惑,但我想说的是,在这种情况下,我宁愿在构造函数中完成这些检查,以确保 Schedule 类隐藏的数据的完整性。

谁说您不能从 Schedule 类的构造函数中调用在单独的类中定义的规则?如果它需要访问非公共字段,您可以将它们设为包私有并将规则类放在同一个包中 - 或将它们作为参数传递给规则。

一般是怎么做的?最佳做法是什么?

是否将验证规则移动到单独的类应该取决于应用程序的需求,而不是误解的流行语。

拥有单独规则类的充分理由是:

  • 规则如此多且如此复杂,以至于将它们全部放入 Schedule 类会使其变得太大。
  • 您并不总是想应用所有规则。
  • 可以重用这些规则来对不一定在同一继承层次结构中的不同类进行操作(即它们通过接口工作)。
于 2009-06-04T08:43:48.573 回答
2

看起来Schedule构造函数有两种不同的行为。因此,应该有两个构造函数。甚至可能有两个实现类。

locationNumchangeReason。这些目前是弱类型的。他们可能应该有自己的班级。根据您的问题,有效值取决于上下文。目前Schedule没有任何上下文。您可以保持独立于上下文,在这种情况下,和Schedule上没有数据库约束。反过来,,和可能依赖于上下文,构造函数应该只检查它们是否都在同一个上下文中。包私有构造函数(充当穷人的)可能会省略检查。locationNumchangeReasonSchedulelocationNumchangeReasonfriend

POJO 意味着一个(写得很好的)对象,因此也意味着行为。这个问题似乎是用它来表示“结构”。避免结构。

业务层似乎具有误导性。它看起来是一个商业实体,它应该有商业行为。但是,它可能不包含业务流程(服务)。

您的应用程序将始终假定有关数据库的某些事情,包括(公共)模式。如果您删除约束,那么您很可能会破坏您的应用程序。这与您以类似方式更改所依赖的代码相同。

通常的做法是使用带有程序代码的贫血对象。在大多数情况下,这不是最佳做法。

于 2009-06-04T09:48:25.683 回答
1

为什么 locationNum 实际上是一个 int,而实际上您指的是另一个类的实例,比如 Location?如果您使用正确的对象引用,您可以在 Schedule 中验证它们配置 ORM(如果有)以强制引用完整性。

于 2009-06-04T08:38:28.617 回答
0

回答您的问题:

1)我不认为你的班级会贫血,现在宁愿成为DTO,我认为这没有问题。

2)如果您的验证在业务层中,这并不意味着您将获得无效对象,因为验证仍然存在,通常您的验证过程会以某种方式抱怨 InvalidDataException 或其他东西并且不允许你使用“损坏”的对象。如果 null 日期不是一个可接受的值(null 通常无法进入数据库),那么业务/dao 层应该捕获此错误并阻止该对象被您的应用程序使用。

于 2009-06-04T09:33:27.397 回答