Reputation: 2132
I'm writing a Plugin Manager which allows user to register new plugin from jar file. PM can be access (read only) from other objects and only one instance of PM is created and injected to other objects. It provides services to manage Plugin as well as a container for list of Plugin. The object is add below
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
....
}
My question is "How should I redesign to avoid global state or mutable state" in such case when new plugin can be registered dynamically at runtime. And immutable data structure can be applied in such case? In my current implementation, PM uses a mutable Map inside it.
Upvotes: 2
Views: 538
Reputation: 11607
First you have to decide which is more important to you--avoiding a global state, or enforcing that only one instance of a plugin manager exists at runtime? Because you can only really get one or the other. If you try to do both, you get a bungled mess like Scala's 'default' ExecutionContext
.
You can avoid global state and mutation by designing your APIs to take in a plugin manager instance if they need one. In that case you need to provide a way to create plugin manager instances at runtime--maybe an ordinary constructor or a smart constructor.
/**
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)
}
Or you can enforce that there is only one plugin manager at runtime by making it a singleton object and referring to it from wherever you need it. But then you're back to needing mutation and worrying about thread safety.
Personally I greatly prefer no mutation--it lifts a huge mental load.
Upvotes: 3