事先为一个长期待解决的问题表示歉意。反馈特别感谢在这里。 。 。

在我的工作中,我们做了很多关于日期范围(日期周期,如果可以的话)的事情。我们需要进行各种测量,比较两个日期周期之间的重叠程度,等等。我设计了一个Interface,一个基类和几个派生类,这些类可以满足我迄今为止的需求:

  • IDate时期
  • DatePeriod
  • CalendarMonth
  • CalendarWeek
  • FiscalYear

  • 除去其本质,DatePeriod父类(super class)如下(省略了所有令人着迷的功能,这是我们为什么需要这套类的基础...):

    (Java伪代码):
    class datePeriod implements IDatePeriod
    
    protected Calendar periodStartDate
    protected Calendar periodEndDate
    
        public DatePeriod(Calendar startDate, Calendar endDate) throws DatePeriodPrecedenceException
        {
            periodStartDate = startDate
            . . .
            // Code to ensure that the endDate cannot be set to a date which
            // precedes the start date (throws exception)
            . . .
            periodEndDate = endDate
        {
    
        public void setStartDate(Calendar startDate)
        {
            periodStartDate = startDate
            . . .
            // Code to ensure that the current endDate does not
            // precede the new start date (it resets the end date
            // if this is the case)
            . . .
        {
    
    
        public void setEndDate(Calendar endDate) throws datePeriodPrecedenceException
        {
            periodEndDate = EndDate
            . . .
            // Code to ensure that the new endDate does not
            // precede the current start date (throws exception)
            . . .
        {
    
    
    // a bunch of other specialty methods used to manipulate and compare instances of DateTime
    
    }
    

    基类包含大量用于操作日期期间类的相当专门的方法和属性。派生类仅更改设置有关期间的起点和终点的方式。例如,对我来说,CalendarMonth对象确实是“是-” DatePeriod才有意义。但是,出于明显的原因,日历月是固定持续时间的,并且具有特定的开始和结束日期。实际上,尽管CalendarMonth类的构造函数与父类(super class)的构造函数匹配(因为它具有startDate和endDate参数),但实际上这是简化构造函数的重载,该构造函数仅需要单个Calendar对象。

    对于CalendarMonth,提供任何日期都将导致CalendarMonth实例开始于相关月份的第一天,并于该月的最后一天结束。
    public class CalendarMonth extends DatePeriod
    
        public CalendarMonth(Calendar dateInMonth)
        {
            // call to method which initializes the object with a periodStartDate
            // on the first day of the month represented by the dateInMonth param,
            // and a periodEndDate on the last day of the same month.
        }
    
        // For compatibility with client code which might use the signature
        // defined on the super class:
        public CalendarMonth(Calendar startDate, Calendar endDate)
        {
            this(startDate)
            // The end date param is ignored.
        }
    
        public void setStartDate(Calendar startDate)
        {
            periodStartDate = startDate
            . . .
        // call to method which resets the periodStartDate
        // to the first day of the month represented by the startDate param,
        // and the periodEndDate to the last day of the same month.
            . . .
        {
    
    
        public void setEndDate(Calendar endDate) throws datePeriodPrecedenceException
        {
            // This stub is here for compatibility with the superClass, but
            // contains either no code, or throws an exception (not sure which is best).
        {
    }
    

    很长的序言道歉。在上述情况下,该类结构似乎违反了Liskov替代原则。尽管在可能使用更通用的DatePeriod类的任何情况下,一个CAN都可以使用CalendarMonth的实例,但是关键方法的输出行为将有所不同。换句话说,必须知道在给定情况下正在使用CalendarMonth的实例。

    虽然CalendarMonth(或CalendarWeek等)遵守通过基类使用IDatePeriod建立的契约(Contract),但在使用CalendarMonth且预期使用普通的DatePeriod的情况下,结果可能会严重扭曲。 。 。 (请注意,在基类上定义的所有其他时髦方法都可以正常工作-只是开始日期和结束日期的设置与CalendarMonth实现的不同)。

    有没有更好的方法来构造这种结构,从而可以在不影响可用性和/或复制代码的情况下保持对LSP的正确遵循?

    最佳答案

    这似乎与关于“正方形”和“矩形”的通常讨论类似。尽管正方形是矩形,但对于Square来说,从矩形继承是没有用的,因为它不能满足矩形的预期行为。

    您的DatePeriod具有setStartDate()和setEndDate()方法。使用DatePeriod,您希望可以以任何顺序调用两者,而不会互相影响,并且可能它们的值将精确地指定开始和结束日期。但是对于CalendarMonth实例,这不是事实。

    也许不是让CalendarMonth扩展DatePeriod,而是两者都可以扩展一个公共(public)的抽象类,该类仅包含与两者兼容的方法。

    顺便说一句,根据您问题的体贴程度,我想您已经考虑过寻找现有的日期库。以防万一,请务必查看Joda time库,该库包含可变和不可变期间的类。如果现有的库解决了您的问题,则您可以专注于自己的软件,并让其他人承担设计,开发和维护时间库的费用。

    编辑:注意,我已经将CalendarMonth类称为Calendar。固定为清楚起见。

    09-27 13:24