Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/385.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 遗传与LSP_Java_Oop_Inheritance_Liskov Substitution Principle - Fatal编程技术网

Java 遗传与LSP

Java 遗传与LSP,java,oop,inheritance,liskov-substitution-principle,Java,Oop,Inheritance,Liskov Substitution Principle,为一个冗长的问题提前道歉。在此特别感谢您的反馈 在我的工作中,我们做了很多关于日期范围的事情(日期周期,如果你愿意的话)。我们需要进行各种测量,比较两个日期周期之间的重叠,等等。到目前为止,我已经设计了一个接口、一个基类和几个派生类,它们很好地满足了我的需求: 假周期 日期周期 日历月 日历周 财政部 简而言之,DatePeriod超类如下(省略了所有吸引人的特性,这些特性是我们为什么需要这组类的基础…) (Java伪代码): 基类包含一组相当专门的方法和属性,用于操作date period

为一个冗长的问题提前道歉。在此特别感谢您的反馈

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

  • 假周期
  • 日期周期
  • 日历月
  • 日历周
  • 财政部
简而言之,DatePeriod超类如下(省略了所有吸引人的特性,这些特性是我们为什么需要这组类的基础…)

(Java伪代码):

基类包含一组相当专门的方法和属性,用于操作date period类。派生类仅更改所述期间的起点和终点的设置方式。例如,对于我来说,CalendarMonth对象实际上是一个日期周期是有意义的。然而,由于明显的原因,日历月的持续时间是固定的,并且有特定的开始和结束日期。事实上,尽管CalendarMonth类的构造函数与超类的构造函数相匹配(因为它有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替换原则。尽管在任何情况下都可以使用CalendarMonth实例,在这种情况下可以使用更通用的DatePeriod类,但关键方法的输出行为将有所不同。换句话说,您必须知道,在给定的情况下,您正在使用CalendarMonth实例

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


有没有更好的方法来构建它,以便在不影响可用性和/或复制代码的情况下保持对LSP的正确遵守?

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

DatePeriod有一个setStartDate()和setEndDate()方法。对于DatePeriod,您可能希望这两个函数可以以任何顺序调用,不会相互影响,而且它们的值可能会精确地指定开始和结束日期。但对于CalendarMonth实例,情况并非如此

也许,这两种方法不必使用CalendarMonth extend DatePeriod,而是可以扩展一个公共抽象类,该类只包含与这两种方法兼容的方法

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


编辑:注意到我将您的CalendarMonth类称为Calendar。为清晰起见,已修复

通常情况下,遵守LSP需要仔细记录基类或接口的功能

例如,在Java
集合中
有一个名为
add(E)
的方法。它可以有以下文档:

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

但是如果是这样的话,
集合
(它保持无重复不变量)就很难不违反LSP。因此,
add(E)
是这样记录的:

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

现在,没有客户端可以使用
集合
,并期望始终添加元素,即使该元素已存在于集合中

我对你的例子看得不太深入,但我觉得你也许可以同样小心。如果在日期周期界面中,
setStartDate()
的文档记录如下:

确保开始日期为指定的日期

没有进一步说明?甚至

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

setEndDate()
可以实现,并且可以类似地记录。那么,具体实施如何打破LSP


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

这肯定违反了LSP,与经典的椭圆和圆示例完全相同

如果希望
CalendarMonth
扩展
DatePeriod
,则应使
DatePeriod
不可变


然后,您可以将所有的变异方法更改为返回新的
DatePeriod
的方法,并使所有内容都保持不变,或者创建替代的可变子类,不尝试处理年、月、,周等等。

我认为建模的问题在于,您的
日历月
类型实际上并不是一种不同的时段。相反,这是一个错误
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).
    {
}
final IDatePeriod period = Periods.wholeMonthBounding(Calendar day);
interface PeriodPredicate
{
  boolean containsMoment(Calendar day);
}
// 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);