Наращиваемый признак / декоратор и абстрактный класс

#scala #design-patterns

#scala #шаблоны проектирования

Вопрос:

У меня есть абстрактный класс (библиотека Java), который принимает аргументы конструктора и имеет вызываемый метод execute , который я хочу украсить:

 public abstract class Task {
    private final String name;

    protected Task(String name) {
        this.name = name;
    }
    public abstract void execute(String str) throws Exception
}
  

И у меня есть классы Scala, которые в настоящее время наследуют предыдущий:

 class FooTask extends Task("fooTask") {
  override def execute(str: String): Unit = println(str   "foo")
}

class BarTask extends Task("barTask") {
  override def execute(str: Strin): Unit = println(str   "bar")
}
  

Возможно ли написать декоратор для класса задач, подобного этому:

 trait TaskWithErrorLogging { self: Task =>

  override def execute(str: String): Unit =
    Try(self.execute(str)) match {
      case Failure(exception) =>
        println("LOGGED "   exception)
        throw exception;
      case _ =>
    }

}
  

А затем использовать его для регистрации ошибок?

 class BarTask extends Task("barTask") with TaskWithErrorLogging {
  override def execute(str: String): Unit = println(str   "bar") // Error should be logged
}
  

Эти задачи создаются автоматически инжектором фреймворка, поэтому нет способа написать new FooTask with TaskWithErrorLogging

В настоящее время переопределенный метод декоратора игнорируется (он компилируется, но не выполняется). Добавление abstract модификатора к методу в декораторе не компилируется. Как правильно реализовать это решение для ведения журнала? Может быть, есть другой вариант, кроме наращиваемого признака?

Комментарии:

1. Я не уверен, правильно ли я понимаю проблему, но если tasks are being instantiated automatically by framework тогда вы просто не можете использовать подход stackable trait decorator, потому что это функция времени компиляции. Создание stack с помощью using with происходит так же, как если бы вы объявили новый класс. Вы не можете объявить класс на основе уже созданного объекта.

2. Я имею в виду, что я не могу объявить это class BarTask extends Task("barTask") , а затем написать new BarTask with TaskWithErrorLogging , я могу только написать class BarTask extends Task("barTask") with TaskWithErrorLogging

3. Как я уже писал, этот подход не работает или не компилируется (зависит от того, добавляете ли вы abstract модификатор к признаку)

4. кстати, ваш признак TaskWithErrorLogging не может быть скомпилирован в scala, потому что он имеет специфичное для Java объявление параметра.

5. Я исправил описание кода, спасибо

Ответ №1:

В настоящее время переопределенный метод декоратора игнорируется (он компилируется, но не выполняется)

Он не выполняется, потому что он переопределен BarTask . И если он выполнялся, он имеет бесконечную рекурсию: self.execute(str) вызовет тот же метод.

Самый простой способ

 trait TaskWithErrorLogging extends Task {
  override def execute(str: String): Unit =
    Try(doExecute(str)) match {
      case Failure(exception) =>
        println("LOGGED "   exception)
        throw exception;
      case _ =>
    }

  def doExecute(str: String): Unit
}

class BarTask extends Task("barTask") with TaskWithErrorLogging {
  override def doExecute(str: String): Unit = println(str   "bar")
}
  

Или, если вы действительно хотите использовать наращиваемые декораторы, TaskWithErrorLogging их все равно нужно смешивать после метода, который он украшает, например

 trait TaskWithErrorLogging extends Task { 
  abstract override def execute(str: String): Unit =
    Try(super.execute(str)) match { // note super, not self!
      case Failure(exception) =>
        println("LOGGED "   exception)
        throw exception;
      case _ =>
    }
}

class BarTask0 extends Task("barTask") {
  override def execute(str: String): Unit = println(str   "bar")
}

class BarTask extends BarTask0 with TaskWithErrorLogging 
  

Комментарии:

1. Вы имеете в виду, что нет другого способа, кроме как добавить другой метод с другим именем вместо execute прямого вызова?