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