2015-11-14 4 views
2

В настоящее время я узнаю о наследовании, и я немного смущен ограничениями, объявляющими, что метод final в суперклассе применяется к подклассам. Скажите, что у меня есть надбавка BankAccount с методом снятия денег, для которой требуется, чтобы пользовательский пароль и сумма были сняты, и устанавливает баланс счета (баланс - сумма). Я хотел бы объявить этот метод окончательным, так что другие подклассы не могли бы переопределить его и позволить клиенту снимать деньги без изменения баланса счета.Перейдите к окончательному методу в подклассе Java?

public final void withdraw(double amount, String pass) { 
    if (checkPassword(pass) && getBalance() >= amount;) { 
     setBalance(getBalance() - amount); 
    } else { 
     System.out.println("Rejected."); 
    } 
} 

Я хочу, чтобы избежать чего-то вроде этого не только не позволили:

public void withdraw(double amount, String pass) { 

} 

Однако некоторые банковские счета позволяют овердрафты, которые также должны быть учтены при принятии вывода. Теперь, если у меня есть подкласс BankAccountOverdraft, унаследованный метод вывода является окончательным, поэтому я не смогу изменить какие-либо его части. Но все-таки, я ДОЛЖЕН учитывать лимит овердрафта в подклассе? Как я могу это сделать?

public void withdraw(double amount, String pass) { 
    if (checkPassword(pass) && getOverDraftLimit() + getBalance() >= amount) { 
     setBalance(getBalance() - amount); 
    } else { 
     System.out.println("Rejected."); 
    } 
} 
+1

Вы сами дали ответ? Внедрите метод getOverDraftLimit в суперклассе со значением по умолчанию и переопределите его в подклассах, которые задают ограничение. – dunni

+0

Почему бы просто не добавить атрибут 'allowedOverdraft' в' BankAccount' и пренебречь 'BankAccountOverdraft'? Другое решение - определить метод 'getAllowedOverdraft()' внутри 'BankAccount' (как не окончательный), который возвращает' '0''. Вы можете перезаписать этот метод в 'BankAccountOverdraft', чтобы вернуть, что вы хотите, чтобы он возвращался, и использовать его в методе' take (...) 'BankAccount'. – Turing85

+0

Кстати, как учебное упражнение это прекрасно, но на самом деле такое ограничение бизнеса (с реальными деньгами на карту), вероятно, никогда не реализуется через наследование. Обычно у вас есть четкие бизнес-правила. – biziclop

ответ

3

Вопрос, который вам нужно задать: это процедура вывода, отличная для учетной записи с овердрафтом? Нет, это не так, процесс один и тот же: вы проверяете пароль, вы проверяете наличие достаточного количества средств, вы дебетете счет.

Другое: check there are enough funds шаг. Поэтому ваша абстракция должна отражать это, как это происходит с checkPassword().

public final void withdraw(double amount, String pass) { 
if (checkPassword(pass) && checkFundsAvailable(amount)) { 
    setBalance(getBalance() - amount); 
} else { 
    System.out.println("Rejected."); 
} 
} 

protected boolean checkFundsAvailable(double amount) { 
    return amount <= getBalance(); 
} 

И когда вы можете иметь овердрафт, то заменить его:

protected boolean checkFundsAvailable(double amount) { 
    return amount <= getBalance() + overdraftLimit; 
} 

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

P.s .: Несмотря на все вышесказанное, есть очень веская причина не подходить к этой проблеме с наследованием (если это была настоящая проблема, а не просто упражнение), но она более тонкая. Имея класс BankAccount и подкласс BankAccountOverdraft, вы также утверждаете, что учетные записи , которые не позволяют овердрафтам, никогда не могут быть превращены в overdrawable и наоборот. Но реальные банковские счета не ведут себя так, вы можете начать с невозможности овердрафта и согласиться с лимитом овердрафта позже. Наследование не способ выразить это, вам нужно будет использовать какую-то форму композиции.

+0

Хорошее объяснение некоторых подводных камней наследования. – Jason

3

Другие предложили изменить ваш дизайн, но важно понять, почему ваш оригинальный дизайн не работает.

Ваш класс BankAccount ведет себя определенным образом при попытке овердрафта. Это поведение является частью его неявного API. Класс, который позволяет овердрафты разрывает этот API на weakening the postcondition, что баланс не может быть отрицательным. В некотором смысле, это не a BankAccount. Поэтому он не должен быть подклассом BankAccount. Создание класса или методов final - это способ реализации Java.

Как примечания biziclop, можно использовать наследование, чтобы выразить связь между учетной записью, которая может овердрафтом, и той, которая не может. Но превращение одного из родителей другого в ловушку Принципа замещения Лискова. Вместо этого сделайте два класса внедрить или расширить общий интерфейс или суперкласс, который не определяет поведение овердрафта. Возможно, суперкласс не должен включать метод withdraw. Избегайте писать один класс, который работает в обоих направлениях. Если клиент вашего API должен проверять поведение и возможности объекта, вы обошли сильную типизацию, которая является одной из самых сильных сторон Java.

Есть отличная книга под названием Эффективная Java, которую должен прочитать каждый программист Java. Он рассказывает о том, как inheritance breaks encapsulation и как каждый класс, который позволяет наследование, должен быть разработан очень тщательно. По этим причинам я считаю, что для студентов ужасно плохо, что наследование является одной из первых вещей, которые преподаются во вводном классе программирования. Это должно быть одно из последних.

+0

Я полностью согласен с этим, но следует отметить, что в этом случае постусловие не обязательно должно быть универсальным. Если ваша модель говорит, что постусловия может отличаться для разных учетных записей, то это можно выразить через наследование. Я бы выделил +1 для комментария о преподавании, если бы мог, 90% примеров наследования, которые я встречал в моем образовании, были недействительными. – biziclop

+0

@biziclop Отредактировано. –

0

Я собираюсь предположить, что ваш пример просто иллюстрирует проблему дизайна. Обычно вы не защищаете ресурс, такой как банковский счет, с чем-то простым, как сравнение строк и ограничение API. Есть ряд других вопросов, таких как условия гонки.

Что вы можете сделать, это разделить withdraw на два отдельных метода. Метод super может быть помечен как final, и у вас может быть второй метод protected, который не помечен final, который фактически снимает. Это заставляет проверку произойти, но позволяет динамическое поведение вывода.

public final void withdraw(double amount, String pass) { 

    if (checkPassword(pass)) { 
     withdraw(amount); 
    } else { 
     System.out.println("Rejected."); 
    } 
} 

protected void withdraw(double amount) { 

    if (getBalance() >= amount) { 
    setBalance(getBalance() - amount); 
    } else { 
    System.out.println("Rejected."); 
    } 
} 

В классе, производный ...

@Override 
protected void withdraw(double amount) { 

    if (getOverDraftLimit() + getBalance() >= amount) { 
    setBalance(getBalance() - amount); 
    } else { 
    System.out.println("Rejected."); 
    } 
} 

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

Смежные вопросы