Есть ли способ сделать метод, который не является абстрактным, но должен быть переопределен?
Есть ли способ заставить дочерние классы переопределить неабстрактный метод суперкласса?
Мне нужно иметь возможность создавать экземпляры родительского класса, но если класс расширяет этот класс, он должен дать свое собственное определение некоторых методов.
13 ответов
Насколько мне известно, не существует прямого способа выполнения этого с помощью компилятора.
Вы можете обойти это, не делая родительский класс инстанцируемым, а вместо этого предоставляя метод фабрики, который создает экземпляр некоторого (возможно частного) подкласса, который имеет реализацию по умолчанию:
public abstract class Base {
public static Base create() {
return new DefaultBase();
}
public abstract void frobnicate();
static class DefaultBase extends Base {
public void frobnicate() {
// default frobnication implementation
}
}
}
Ты не можешь писать new Base()
сейчас, но вы можете сделать Base.create()
чтобы получить реализацию по умолчанию.
Как уже отмечали другие, вы не можете сделать это напрямую.
Но один из способов сделать это - использовать шаблон "Стратегия" следующим образом:
public class Base {
private final Strategy impl;
// Public factory method uses DefaultStrategy
// You could also use a public constructor here, but then subclasses would
// be able to use that public constructor instead of the protected one
public static Base newInstance() {
return new Base(new DefaultStrategy());
}
// Subclasses must provide a Strategy implementation
protected Base(Strategy impl) {
this.impl = impl;
}
// Method is final: subclasses can "override" by providing a different
// implementation of the Strategy interface
public final void foo() {
impl.foo();
}
// A subclass must provide an object that implements this interface
public interface Strategy {
void foo();
}
// This implementation is private, so subclasses cannot access it
// It could also be made protected if you prefer
private static DefaultStrategy implements Strategy {
@Override
public void foo() {
// Default foo() implementation goes here
}
}
}
Подумайте о создании интерфейса с этим методом. Потомки класса должны будут это реализовать.
Я думаю, что самый простой способ - создать абстрактный класс, который наследуется от базового класса:
public class Base {
public void foo() {
// original method
}
}
abstract class BaseToInheritFrom extends Base {
@Override
public abstract void foo();
}
class RealBaseImpl extends BaseToInheritFrom {
@Override
public void foo() {
// real impl
}
}
Есть причина, по которой это невозможно!
Производный класс может просто вызвать реализацию базового класса при переопределении метода.
Так какой смысл заставлять класс переопределять ваш метод? Я не думаю, что есть какая-то польза.
Нет, в этом весь смысл абстрактного метода. Какой у вас вариант использования? Возможно, мы можем думать об этом исходя из основных потребностей.
Как насчет этого: внутри реализации метода по умолчанию используйте отражение, чтобы получить точный класс объекта. Если класс не совсем соответствует вашему базовому классу, выведите исключение RuntimeException или его эквивалент.
public class Parent {
public void defaultImpl(){
if(this.getClass() != Parent.class){
throw new RuntimeException();
}
}
}
Ответом будет нет. Вы можете изменить дизайн, используя шаблон дизайна шаблона. Это может помочь вам.
Кроме того, вы можете заставить свои дочерние классы реализовывать интерфейс. Интерфейс может быть или не быть реализован суперклассом.
Вы всегда можете сделать так, чтобы у базового класса был метод, который выдает исключение.
Технически базовый класс определил метод, но его нельзя использовать без переопределения метода. В таком случае я предпочитаю исключение времени выполнения, поскольку они не требуют явного оператора throws. Пример следует
public class Parent {
public void doStuff() {
throw new RuntimeException("doStuff() must be overridden");
}
}
public class Child extends Parent {
@Override
public void doStuff() {
... all is well here ...
}
}
Недостатком является то, что это не мешает созданию Base
объекты; однако любой, кто пытается использовать один из методов "должен быть переопределен", вскоре обнаружит, что он должен переопределить класс.
Хотя это решение хорошо соответствует описанию запроса, вашему приложению, вероятно, будет полезно не требовать решения, подобного этому. Гораздо лучше избегать сбоев во время выполнения с помощью проверок компилятора, что обеспечивает абстрактное ключевое слово.
Возможно это поможет:
class SuperClass {
void doStuff(){
if(!this.getClass().equals(SuperClass.class)){
throw new RuntimeException("Child class must implement doStuff Method");
}else{
//ok
//default implementation
}
}
}
class Child extends SuperClass{
@Override
void doStuff() {
//ok
}
}
class Child2 extends SuperClass{
}
new SuperClass().doStuff(); //ok
new Child().doStuff(); //ok
new Child2().doStuff(); //error
Я отразлю другие ответы и скажу, что не существует принудительного способа компиляции производных классов переопределять неабстрактный метод. Весь смысл создания абстрактного метода состоит в том, чтобы определить, что метод с этой сигнатурой должен существовать, но не может быть указан на базовом уровне и, следовательно, должен быть указан на производном уровне. Если есть работающая, нетривиальная реализация (так как она не пустая и не просто выдает исключение или показывает сообщение) метода на базовом уровне, то это не является строго необходимым для вызова метод от потребителя производного класса, чтобы преуспеть. Таким образом, компилятору не нужно принудительно переопределять метод, который может довольно успешно работать на базовом или производном уровнях.
В ситуациях, когда вы хотите, чтобы производный класс переопределял вашу рабочую реализацию, должно быть совершенно очевидно, что базовая реализация не делает то, что хотят потребители производного класса; базовый класс либо не имеет достаточной реализации, либо неправильной. В этих случаях вы должны верить, что программист, унаследованный от вашего класса, будет знать, что он делает, и, следовательно, знает, что метод должен быть переопределен, поскольку он не дает правильного ответа в контексте использования его нового объекта.
Я могу думать об одной вещи, которую ты мог бы сделать. Это потребовало бы абстрактной базы с запечатанной (окончательной для Javaheads) реализацией "по умолчанию". Таким образом, есть базовая реализация метода, которую легко использовать, как если бы это был "базовый" класс, но для определения другого класса для нового сценария необходимо вернуться к абстрактному классу, и Таким образом, вынуждены переопределить метод. Этот метод может быть единственной абстрактной вещью в классе, тем не менее позволяя вам использовать базовые реализации других методов:
public abstract class BaseClass
{
public abstract void MethodYouMustAlwaysOverride();
public virtual void MethodWithBasicImplementation() { ... }
}
public final class DefaultClass:BaseClass
{
public override void MethodYouMustAlwaysOverride() { ... }
//the base implementation of MethodWithBasicImplementation
//doesn't have to be overridden
}
...
public class DerivedClass:BaseClass
{
//Because DefaultClass is final, we must go back to BaseClass,
//which means we must reimplement the abstract method
public override void MethodYouMustAlwaysOverride() { ... }
//again, we can still use MethodWithBasicImplementation,
//or we can extend/override it
public override void MethodWithBasicImplementation() { ... }
}
Однако это имеет два недостатка. Во-первых, поскольку у вас нет доступа к реализации DefaultClass посредством наследования, вы не можете расширить реализацию DefaultClass, то есть, чтобы сделать то, что делает DefaultClass, плюс еще немного, вы должны переписать код из DefaultClass, нарушая DRY. Во-вторых, это работает только для одного уровня наследования, потому что вы не можете принудительно переопределить, если разрешаете наследование от DerivedClass.
Это не может быть рекомендовано, но вы можете добавить исключение (что-то вроде MethodeMustBeOverRiddenExp) в вашу реализацию метода. Конечно, это форсирование RunTime, но может быть и лучше, чем чокнуться.
Хорошо, давайте изучим это так. Я придерживаюсь руководящих принципов стиля Java и использую синтаксис Java. Поэтому предполагается, что множественное наследование и шаблоны C++ недоступны.
Делать метод родительского класса абстрактным не обязательно, в ООП вы используете концепцию полиморфизма. Вы можете использовать один и тот же метод двумя или более разными способами. Это называется переопределением метода.
давайте возьмем пример.
public class Animal{
public void makeSound(){
System.out.println("Animal doesn't know how to make sound");
}
}
public class PussyCat extends Animal{
public void makeSound(){
System.out.println("meowwww !!!");
}
public static void main(String args[]){
PussyCat aCat=new PussyCat();
aCat.makeSound();
}
}
это напечатает "мяуууу!!!" на экране.
но это не означает, что метод makeSound должен быть переопределен в дочернем классе.
Если вам нужно, чтобы дочерний класс был вынужден переопределить методы, вам лучше реализовать интерфейс класса.
public Interface audible{
public void makeSound();
}
public class pussyCat implements audible{
// now you must implement the body of makeSound method here
public void makeSound(){
System.out.println("meowwww !!!");
}
public static void main(String args[]){
PussyCat aCat=new PussyCat();
aCat.makeSound();
}
}
это также напечатает "мяуууууу!!!" на экране