Могу ли я сказать, был ли вызван абстрактный метод?

Учитывая этот класс:

abstract class Foo{
    public Foo(){...}

    public abstract void checkable();

    public void calledWhenCheckableIsCalled(){
        System.out.println("checkable was called");
    }
}

Есть ли код, который я могу поместить в конструктор Foo, чтобы сделать calledWhenCheckableIsCalled позвонить, когда checkable называется?

Примечание. Это грубое упрощение реального проекта, над которым я работаю.

Изменить: я уже реализовал шаблон обхода. Мне просто интересно, есть ли другой способ сделать это, которого мне не хватает. (Возможно, используя отражение.)

6 ответов

Решение

Выглядит как шаблон шаблона.

Но тогда вы должны реализовать Foo.checkable() и ввести другой абстрактный метод для делегирования.

abstract class Foo{
    public Foo(){}

    public void checkable(){
        calledWhenCheckableIsCalled();            
        doCheckable();
    } 

    protected abstract void doCheckable();

    public void calledWhenCheckableIsCalled(){
        System.out.println("checkable was called");
    }
}

Я бы также предложил сделать checkable() окончательный в этом случае, так что вы можете быть уверены, что checkable() не может быть реализовано по-другому, как вы ожидали.

В дополнение к комментарию Брайана Роуча

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

Это правда, но вы можете предотвратить Foo экземпляр от экземпляра, если подкласс увеличивает видимость doCheckable, Поэтому вы должны вводить проверку всякий раз, когда создается объект. Я бы порекомендовал использовать код инициализатора, чтобы проверка выполнялась для каждого существующего конструктора. Тогда нельзя забывать призывать и, следовательно, быть обойденным.

Например:

abstract class Foo {
    { // instance initializer code ensures that enforceDoCheckableVisibility
      // is invoked for every constructor
        enforceDoCheckableVisibility();
    }
    public Foo() {...}
    public Foo(Object o) {...}

    private void enforceDoCheckableVisibility() {
        Class<?> currentClass = getClass();
        while (currentClass != Foo.class) {
            try {
                Method doCheckableMethod = currentClass.getDeclaredMethod("doCheckable");
                if (Modifier.isPublic(doCheckableMethod.getModifiers())) {
                    throw new RuntimeException("Visibility of "
                                  + currentClass.getSimpleName()
                                  + ".doCheckable() must not be public");
                }
            } catch (SecurityException | NoSuchMethodException e) {}
            currentClass = currentClass.getSuperclass();
        }
    }
}

Поскольку проверка реализована с использованием отражения, недостатком является то, что она проверяется только во время выполнения. Так что, конечно, у вас не будет поддержки компилятора. Но этот подход позволяет вам обеспечить, чтобы экземпляр Foo может существовать, только если он выполняет ваш контракт.

Нет, конструктор будет вызван один раз во время инициализации объекта. Однако вы можете получить свой подкласс, который обеспечивает реализацию для вызова метода в суперклассе:

class Bar extends Foo {

    // implementation of abstract method
    public void checkable(){
        super.calledWhenCheckableIsCalled(); // call to parent's method
        ...
    }
}

РЕДАКТИРОВАТЬ

Вы могли бы достичь этого с помощью аспектов. Используя аспект, вы можете перехватывать каждый вызов метода, ссылаясь на абстрактный родительский метод. Это освобождает вас от вмешательства в детский код. Ваш calledWhenCheckableIsCalled код затем станет частью перехватывающего кода.

abstract class Foo {

    // use pointcut to intercept here
    public void checkable();
}

Мне кажется, что вы ищете шаблон шаблона:

public abstract class Template {
    public final void checkable() {
        calledWhenCheckableIsCalled();
        doCheckable();
    }

    protected abstract void doCheckable();

    private  void calledWhenCheckableIsCalled() {
        System.out.println("checkable was called");
    }
}

Теперь каждый раз checkable() называется, calledWhenCheckableIsCalled() также называется. И наследник должен по-прежнему обеспечивать фактическую реализацию checkable(), осуществляя doCheckable() метод.

Обратите внимание, что делает checkable() final не позволяет подклассу переопределить его и, таким образом, обойти вызов calledWhenCheckableIsCalled(),

abstract class foo {

    public abstract void bar();

    public void moo() {
        System.out.println("some code");

        this.bar();

        System.out.println("more code");
    }
}

сейчас если moo называется, основная реализация bar будет использоваться, это просто небольшой сдвиг парадигмы от того, что вы хотите.

так ваш конечный пользователь будет звонить moo вместо bar, но он все еще должен реализовать bar

Нет, у абстрактного метода нет тела. Вы можете, однако, связать ваш метод следующим образом:

abstract class Foo {

    void callMeInstead() {

        // do common

        callMeImplementation();
    }

    abstract void callMeImplementation();
}

Там нет никакого способа, как вы заставляете этот метод реализовать в child,

Неловкое предложение будет известно из реализации ребенка. Я имею в виду нет чистого пути AFAIK

Другие вопросы по тегам