首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >继承和LSP

继承和LSP
EN

Stack Overflow用户
提问于 2011-06-18 22:06:27
回答 4查看 748关注 0票数 5

为一个冗长的问题事先道歉。这里的反馈尤其值得赞赏。。。

在我的工作中,我们做了很多有日期范围的事情(如果你愿意的话,日期周期)。我们需要进行各种测量,比较两个日期周期之间的重叠,等等。我设计了一个接口、一个基类和几个派生类,它们很好地满足了我的需要:

  • IDatePeriod
  • DatePeriod
  • CalendarMonth
  • CalendarWeek
  • FiscalYear

除去它的本质,DatePeriod超类如下所示(忽略所有吸引人的特性,这些特性是我们为什么需要这组类的基础。。.)):

(Java伪码):

代码语言:javascript
复制
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类的构造函数与超类的构造函数匹配(因为它有一个startDate和endDate参数),但这实际上是一个简化构造函数的过载,它只需要一个Calendar对象。

在CalendarMonth的情况下,提供任何日期都会导致一个CalendarMonth实例,该实例从这个月的第一天开始,并在同一个月的最后一天结束。

代码语言:javascript
复制
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替换原则。尽管在任何情况下都可以使用CalendarMonth的实例,在这种情况下可以使用更通用的DatePeriod类,但是关键方法的输出行为将是不同的。换句话说,您必须知道在给定的情况下使用的是CalendarMonth实例。

而CalendarMonth (或CalendarWeek等)遵循通过基类使用IDatePeriod建立的契约,在使用CalendarMonth和预期普通旧DatePeriod行为的情况下,结果可能会严重倾斜。。。(请注意,在基类上定义的所有其他时髦方法都正常工作-在CalendarMonth实现中,只有开始日期和结束日期的设置不同)。

是否有更好的方法来构建这样的结构,以便在不影响可用性和/或重复代码的情况下保持对LSP的适当依附?

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2011-06-18 22:11:02

这似乎类似于通常关于平方和矩形的讨论。虽然正方形是-一个矩形,但是对于正方形来说,从矩形继承是没有用的,因为它不能满足矩形的预期行为。

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

也许,与CalendarMonth扩展DatePeriod不同,两者都可以扩展一个公共抽象类,该类只包含与两者兼容的方法。

顺便说一句,基于你问题的深思,我猜你已经考虑过寻找现有的日期库了。如果您没有这样做,请务必查看Joda时间库,它包括用于可变和不可变句点的类。如果一个现有的库解决了您的问题,您可以专注于您自己的软件,并让其他人支付设计、开发和维护时间库的成本。

编辑:注意到我将您的CalendarMonth类称为日历。为了清晰起见。

票数 6
EN

Stack Overflow用户

发布于 2011-06-18 22:40:26

我认为建模的问题是,您的CalendarMonth类型实际上不是一个不同的阶段。相反,它是一个构造函数,或者,如果您愿意的话,可以使用工厂函数来创建这样的句点。

我将消除CalendarMonth类,创建一个类似于Periods的实用工具类,其中包含一个私有构造函数和各种返回各种IDatePeriod实例的公共静态方法。

有了这个,你就可以写

代码语言:javascript
复制
final IDatePeriod period = Periods.wholeMonthBounding(Calendar day);

wholeMonthBounding()函数的文档将解释调用方对返回的IDatePeriod实例的期望。Bikeshedding,这个函数的替代名称可以是wholeMonthContaining()

考虑一下你打算如何处理你的“月经”。如果目标是进行“包含测试”,如“这个时刻是否处于某个时间段内?”,那么您可能需要承认无限和半有界的周期。

这意味着您应该定义一些包含谓词类型,例如

代码语言:javascript
复制
interface PeriodPredicate
{
  boolean containsMoment(Calendar day);
}

然后前面提到的Periods类--也许更好地用这个精化器命名为PeriodPredicates --可以公开更多的函数,例如

代码语言:javascript
复制
// First, some absolute periods:
PeriodPredicate allTime(); // always returns true
PeriodPredicate everythingBefore(Calendar end);
PeriodPredicate everythingAfter(Calendar start);
enum Boundaries
{
  START_INCLUSIVE_END_INCLUSIVE,
  START_INCLUSIVE_END_EXCLUSIVE,
  START_EXCLUSIVE_END_INCLUSIVE,
  START_EXCLUSIVE_END_EXCLUSIVE
}
PeriodPredicate durationAfter(Calendar start, long duration, TimeUnit unit,
                              Boundaries boundaries);
PeriodPredicate durationBefore(Calendar end, long duration, TimeUnit unit
                               Boundaries boundaries);

// Consider relative periods too:
PeriodPredicate inThePast();   // exclusive with now
PeriodPredicate inTheFuture(); // exclusive with now
PeriodPredicate withinLastDuration(long duration, TimeUnit unit); // inclusive from now
PeriodPredicate withinNextDuration(long duration, TimeUnit unit); // inclusive from now
PeriodPredicate withinRecentDuration(long pastOffset, TimeUnit offsetUnit,
                                     long duration, TimeUnit unit,
                                     Boundaries boundaries);
PeriodPredicate withinFutureDuration(long futureOffset, TimeUnit offsetUnit,
                                     long duration, TimeUnit unit,
                                     Boundaries boundaries);

那应该够了。如果你需要澄清的话请告诉我。

票数 2
EN

Stack Overflow用户

发布于 2011-06-18 22:21:58

通常情况下,坚持LSP是一个对基类或接口所做的事情进行详细记录的问题。

例如,在Java中,Collection有一个名为add(E)的方法。它可以有这样的文件:

将指定的元素添加到此集合中。

但是如果是这样的话,那么Set就很难不违反LSP,因为它保持了不重复不变。因此,add(E)的文档如下所示:

确保此集合包含指定的元素(可选操作)。

现在,没有任何客户端可以使用Collection,并且期望元素总是会被添加,即使元素已经存在于集合中。

我对你的例子并没有看得太深入,但我觉得你可能会同样小心。如果在您的日期期间接口中,setStartDate()被记录如下:

确保开始日期是指定的日期。

而没有进一步说明什么?甚至,

确保开始日期是指定的日期,可以选择更改结束日期以维护子类的任何特定不变量。

setEndDate()可以实现,并以类似的方式记录下来。那么,一个具体的实施将如何破坏LSP?

注意到还值得一提的是,如果您使类不可变,那么满足LSP要容易得多。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6399171

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档