Найдите способ конвертировать реализацию var в val - PullRequest
1 голос
/ 19 февраля 2020

Я использую следующий интерфейс из библиотеки Java:

// This is a Java class from a library I'm using
public interface Listener {
  void receiveConfigInfo(final String configInfo);
}

И я расширяю его следующим образом, в Scala:

//MyClass needs to extends the Listener interface
final class MyClass extends Listener {
  private var config = Option.empty

  // Implement the the Listener interface
  override def receiveConfigInfo(configInfo: String): Unit = {
    try {
      config  = decode[Map[String, String]](configInfo) match {
        case Right(config) => Some(config)
        case Left(_) => None
      }
    } catch {
      case _: Throwable => nacosConfig = None
    }
  }

  override def getConfig():Option[Map[String, String]] = nacosConfig
}

receiveConfigInfo будет вызываться автоматически всякий раз, когда это необходимо.

getConfig возвращает последнее значение конфигурации.

Есть ли способ сделать config в val вместо изменяемый var? Я не могу изменить подпись receiveConfigInfo, так как она должна учитывать подпись родительского класса.

Цель - всякий раз, когда я вызываю getConfig, я должен получить последнее значение конфигурации. Однако моя текущая реализация имеет var, что не хорошо, есть ли способ изменить этот код, чтобы сделать его val или другим способом, если это возможно?

Ответы [ 2 ]

0 голосов
/ 19 февраля 2020

Если вы действительно хотите использовать val, вы можете использовать одну из изменяемых структур в collection.mutable . Тем не менее, это было бы более окольным, возможно подверженным ошибкам, и закулисным, фактически используя var, так что вы могли бы также напрямую использовать var (так, как ваш код уже есть).

// in class structure
val config = collection.mutable.IndexedSeq[Option[Map[String, String]]](None)
...
// in receiveConfigInfo
config(0) = decode ...

// in getConfig
config(0)
0 голосов
/ 19 февраля 2020

Учитывая, что реализуемый вами интерфейс Java, кажется, требует, чтобы реализация принимала произвольно много новых configInfo s, я бы сказал, что самое честное - это оставить переменную, но инкапсулированную так, чтобы она могла только может быть изменено с помощью receiveConfigInfo.

Так что, возможно, что-то вроде

object MyClass {
  private[MyClass] Config {
    private var underlying: Map[String, String] = Map.empty

    override def get(key: String): Option[String] = 
      synchronized { underlying.get(key) }

    override def toMap: Map[String, String] =
      synchronized { underlying }

    override def update(configInfo: String): Unit =
      synchronized {
        // I'm assuming that decode is somewhere statically accessible
        // I'm also assuming that a function returning an `Either` won't throw non-fatal exceptions... if it can return a Left, a Right, or throw, then:
        //   Try { decode[...](configInfo) }.flatMap(_.left.map(msg => new RuntimeException(msg)).toTry)
        val decoded =
          decode[Map[String, String]](configInfo)
            .left
            .map(_ => new RuntimeException("decode failed")
            .toTry
        decoded.foreach { newConfig => underlying = newConfig }
        decoded.recoverWith { _ =>
          underlying = Map.empty
          decoded
        }
      }
  }
}

class MyClass extends Listener {
  private val config = new MyClass.Config

  override def receiveConfigInfo(configInfo: String): Unit = config.update(configInfo)
}

При таком решении конфигурация неизменна для всего в MyClass, которое не вызывает update (что, надо надеяться, достаточно очевидно по сравнению с присвоением var.

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

...