避免Scala中的全局状态

时间:2017-02-11 00:13:01

标签: scala

我正在编写一个插件管理器,允许用户从jar文件注册新的插件。 PM可以从其他对象访问(只读),只创建一个PM实例并将其注入其他对象。它提供管理插件的服务以及插件列表的容器。该对象在下面添加

class PluginManager {
    private val plugins = new mutable.Map[String, Plugin]

    def add(plugin: Plugin): PluginManager = {
        plugins.synchronized {
           plugins.put(plugin.id, plugin)
        }
        this
    }
    def remove(id: String): Plugin
    ....
}

我的问题是“如果可以在运行时动态注册新插件,我应该如何重新设计以避免全局状态或可变状态”。并且在这种情况下可以应用不可变数据结构?在我目前的实现中,PM在其中使用了一个可变的Map。

1 个答案:

答案 0 :(得分:3)

首先,您必须决定哪个对您更重要 - 避免全局状态,或者强制在运行时只存在一个插件管理器实例?因为你只能真正获得一个或另一个。如果你试图同时做这两件事,你会得到像Scala的'默认'ExecutionContext一样的混乱。

通过设计API以获取插件管理器实例(如果需要),可以避免全局状态和变异。在这种情况下,您需要提供一种在运行时创建插件管理器实例的方法 - 可能是普通的构造函数或智能构造函数。

/**
Wraps a mapping from plugin IDs to plugin objects. This is a value
type--it gets unwrapped into just the raw map at runtime (the
PluginManager type exists at compile time only).

Also the constructor is private, so users can't directly create new
PluginManager instances.
*/
class PluginManager private (private val plugins: Map[String, Plugin])
  extends AnyVal {
  /**
  We don't need to worry about thread safety because there's no mutation
  here--only pure transformations.
  */
  def add(plugin: Plugin): PluginManager =
    new PluginManager(plugins + (plugin.id -> plugin))

  /**
  Again, no need to worry about thread safety--we get it for free.
  */
  def remove(id: String): PluginManager =
    new PluginManager(plugins - id)
}

object PluginManager {
  /**
  We can get our initial plugin manager here.

  Usage:

  PluginManager.empty add plugin remove plugin
  */
  def empty: PluginManager = new PluginManager(Map.empty)
}

或者您可以通过将其作为单个对象并在任何需要的地方引用它来强制执行只有一个插件管理器。但是,你又回到了需要变异和担心线程安全的问题。

我个人非常不喜欢突变 - 它会带来巨大的心理负担。