Наследование и LSP

Заранее извиняюсь за многословный вопрос. Отзывы особенно ценятся здесь. . .

В моей работе мы много чего делаем с диапазонами дат (дата периоды , если хотите). Нам нужно провести всевозможные измерения, сравнить перекрытие между двумя периодами дат и т. Д. Я разработал интерфейс, базовый класс и несколько производных классов, которые хорошо удовлетворяют мои потребности на сегодняшний день:

  • IDatePeriod
  • DatePeriod
  • ] CalendarMonth
  • CalendarWeek
  • FiscalYear

Урезанный до сути суперкласс DatePeriod выглядит следующим образом (опускает все интересные особенности, которые являются основой того, почему нам нужен этот набор классов ...):

(Псевдокод 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 совпадает с конструктором суперкласса (в том смысле, что он имеет параметры 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).
    {
}

Прошу прощения за длинную преамбулу. Учитывая вышеизложенную ситуацию, может показаться, что эта структура классов нарушает принцип подстановки Лискова. Хотя можно использовать экземпляр CalendarMonth в любом случае, когда можно использовать более общий класс DatePeriod, поведение вывода ключевых методов будет другим. Другими словами, нужно знать, что в данной ситуации используется экземпляр CalendarMonth.

Хотя CalendarMonth (или CalendarWeek и т. Д.) Придерживается контракта, установленного посредством использования IDatePeriod базовым классом, результаты могут сильно искажаться в ситуации, когда использовался CalendarMonth, а поведение обычного старого DatePeriod ожидалось. . . (Обратите внимание, что ВСЕ другие фанковые методы, определенные в базовом классе, работают правильно - в реализации CalendarMonth различаются только настройки начальной и конечной дат.)

Есть ли лучший способ структурировать это так, чтобы обеспечить надлежащее соблюдение в LSP без ущерба для удобства использования и / или без дублирования кода?

5
задан Benjamin 10 November 2013 в 01:04
поделиться