Необязательная реализация метода в абстрактном классе

Я работаю над некоторой структурой, и у меня есть абстрактный класс, который должен быть реализован.

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

Итак, вместо абстрактного метода:

public abstract class AbstractModule {
    public void doSomething() {
        if (logMessage() != null)
            System.out.println(logMessage());
        doStuff();
    }

    protected abstract String logMessage(); // I'm optional
    protected abstract void doStuff();
}

Я думал просто проверять реализацию интерфейса:

public interface Log {
    String logMessage();
}

public abstract class AbstractModule {
    public void doSomething() {
        if (this instanceof Log) {
            if (((Log) this).logMessage() != null)
                System.out.println(((Log) this).logMessage());
        }
        doStuff();
    }

    protected abstract void doStuff();
}

Итак, если кто-то реализует AbstractModule с интерфейсом Log, он также покажет сообщение. Выгода для исполнителя, которого я вижу: ей не нужно заботиться о внедрении logMessage(), как это было бы в первом примере. Является ли это действительным подходом или он должен выполняться по-другому?

Спасибо заранее!

С наилучшими пожеланиями

Ответ 1

Я бы сделал Logger компонентом вашего модуля и определил по умолчанию no-op logger в абстрактном классе. Таким образом вы избавляетесь от instanceof и сохраняете гибкость.

interface Log {
    void logMessage();
}

public abstract class AbstractModule {
    protected Log log;

    public AbstractModule(){
        this.log = () -> {};
    }

    public AbstractModule(Log log){
        this.log = log;
    }

    public void doSomething() {        
        log.logMessage();        
        doStuff();
    }

    protected abstract void doStuff();
}

Вот пример класса, расширяющий AbstractModule:

public class Module extends AbstractModule{

    public Module(){
        super(() -> System.out.println("message"));         
    }

    @Override
    protected void doStuff() {
        // do stuff     
    }   

}

Вы можете определить метод getter для регистратора в абстрактном классе, если вы хотите открыть журнал:

public Log getLogger(){
    return log;
}

Ответ 2

Поскольку этот вопрос отмечен как java-8, альтернативным решением было бы использовать методы interface и default:

public interface Module {
    public default void doSomething() {
        if (logMessage() != null)
            System.out.println(logMessage());
        doStuff();
    }

    public default String logMessage() { return null; } // I'm optional
    public void doStuff();
}

Использование:

class NoLogModule implements Module {
    @Override
    public void doStuff() { }
}

class LogModule implements Module {

    @Override
    public void doStuff() { }

    @Override
    public String logMessage() { return "Message "; }

}

Одним из преимуществ использования этого подхода вместо абстрактного класса является то, что ваш класс теперь может свободно распространяться из другого класса. Один из недостатков этого подхода заключается в том, что вы ничего не можете сделать, чтобы остановить кого-то от переопределения метода doSomething (на основе вашего кода это не похоже на то, как вы заботитесь об этом)

Ответ 3

Достижение instanceof - это немного запаха кода; там обычно лучший способ сделать это.

Мой первый инстинкт - иметь метод no-op в базовом классе:

class AbstractModule {
  final void doSomething() {
    maybeLogMessage();
  }

  void maybeLogMessage() {}
}

который явно ничего не делает; но затем вы можете переопределить в подклассе:

class Subclass extends AbstractModule {
  @Override void maybeLogMessage() {
    System.out.println("The message");
  }
}

который будет печатать сообщение.

Если вы не хотите повторять System.out.println во всех подклассах, вы можете вернуть специальное значение из метода, чтобы указать, что он не должен регистрироваться. Например, вы можете потребовать, чтобы строка была непустой:

class AbstractModule {
  final void doSomething() {
    String message = logMessage();
    if (!message.isEmpty()) { System.out.println(message); }
  }

  String logMessage() { return ""; }
}

class Subclass extends AbstractModule {
  @Override String logMessage() {
    return "The message";
  }
}