这是my previous initialization variable question的后续问题。
假设我们正在处理这个背景:
object AppProperties {
private var mgr: FileManager = _
def init(config: Config) = {
mgr = makeFileManager(config)
}
}
此代码存在的问题是AppProperties
中的任何其他方法都可能会重新分配mgr
。是否有一种技术可以更好地封装mgr
,使其感觉像其他方法的val
?我想过这样的事情(灵感来自this answer):
object AppProperties {
private object mgr {
private var isSet = false
private var mgr: FileManager = _
def apply() = if (!isSet) throw new IllegalStateException else mgr
def apply(m: FileManager) {
if (isSet) throw new IllegalStateException
else { isSet = true; mgr = m }
}
}
def init(config: Config) = {
mgr(makeFileManager(config))
}
}
...但这对我来说感觉相当重要(初始化让我想起了太多的C ++ :-))。还有其他想法吗?
答案 0 :(得分:7)
你可以用implicits做到这一点,使隐式只在应该能够重新分配的方法中可用。查看该值不需要隐式,因此“变量”对其他方法可见:
sealed trait Access
trait Base {
object mgr {
private var i: Int = 0
def apply() = i
def :=(nv: Int)(implicit access: Access) = i = nv
}
val init = {
implicit val access = new Access {}
() => {
mgr := 5
}
}
}
object Main extends Base {
def main(args: Array[String]) {
println(mgr())
init()
println(mgr())
}
}
答案 1 :(得分:4)
好的,所以这是我的建议,直接受到axel22,Rex Kerr和Debilski的答案的启发:
class SetOnce[T] {
private[this] var value: Option[T] = None
def isSet = value.isDefined
def ensureSet { if (value.isEmpty) throwISE("uninitialized value") }
def apply() = { ensureSet; value.get }
def :=(finalValue: T)(implicit credential: SetOnceCredential) {
value = Some(finalValue)
}
def allowAssignment = {
if (value.isDefined) throwISE("final value already set")
else new SetOnceCredential
}
private def throwISE(msg: String) = throw new IllegalStateException(msg)
@implicitNotFound(msg = "This value cannot be assigned without the proper credential token.")
class SetOnceCredential private[SetOnce]
}
object SetOnce {
implicit def unwrap[A](wrapped: SetOnce[A]): A = wrapped()
}
我们得到编译时的安全性:=
没有被意外调用,因为我们需要对象的SetOnceCredential
,它只返回一次。如果调用者具有原始凭证,仍然可以重新分配var 。这适用于AnyVal
和AnyRef
。隐式转换允许我在许多情况下直接使用变量名称,如果这不起作用,我可以通过附加()
显式转换它。
典型用法如下:
object AppProperties {
private val mgr = new SetOnce[FileManager]
private val mgr2 = new SetOnce[FileManager]
val init /*(config: Config)*/ = {
var inited = false
(config: Config) => {
if (inited)
throw new IllegalStateException("AppProperties already initialized")
implicit val mgrCredential = mgr.allowAssignment
mgr := makeFileManager(config)
mgr2 := makeFileManager(config) // does not compile
inited = true
}
}
def calledAfterInit {
mgr2 := makeFileManager(config) // does not compile
implicit val mgrCredential = mgr.allowAssignment // throws exception
mgr := makeFileManager(config) // never reached
}
如果在同一文件中的某个其他位置,我尝试获取另一个凭据并重新分配变量(如在calledAfterInit
中),但在运行时失败,则不会产生编译时错误。< / p>
答案 2 :(得分:2)
我假设您不需要使用基元有效地执行此操作,并且为了简单起见,您也不需要存储null
(但如果这些假设为假,您当然可以修改该想法):
class SetOnce[A >: Null <: AnyRef] {
private[this] var _a = null: A
def set(a: A) { if (_a eq null) _a = a else throw new IllegalStateException }
def get = if (_a eq null) throw new IllegalStateException else _a
}
只需在需要该功能的地方使用此类。 (也许您更希望apply()
到get
?)
如果你真的希望它看起来像一个变量(或方法)访问而没有额外的技巧,那么将SetOnce设为私有,并且
private val unsetHolder = new SetOnce[String]
def unsetVar = unsetHolder.get
// Fill in unsetHolder somewhere private....
答案 3 :(得分:2)
这不是最好的方式而不是你要求的,但它给你一些访问的封装:
object AppProperties {
def mgr = _init.mgr
def init(config: Config) = _init.apply(config)
private object _init {
var mgr: FileManager = _
def apply(config: Config) = {
mgr = makeFileMaker(config)
}
}
}
答案 4 :(得分:2)
关注JPP’s post我做了另一种变体:
class SetOnce[T] {
private[this] var value: Option[T] = None
private[this] var key: Option[SetOnceCredential] = None
def isSet = value.isDefined
def ensureSet { if (value.isEmpty) throwISE("precondition violated: uninitialized value") }
def apply() = value getOrElse throwISE("uninitialized value")
def :=(finalValue: T)(implicit credential: SetOnceCredential = null): SetOnceCredential = {
if (key != Option(credential)) throwISE("Wrong credential")
else key = Some(new SetOnceCredential)
value = Some(finalValue)
key get
}
private def throwISE(msg: String) = throw new IllegalStateException(msg)
class SetOnceCredential private[SetOnce]
}
private val mgr1 = new SetOnce[FileManager]
private val mgr2 = new SetOnce[FileManager]
val init /*(config: Config)*/ = {
var inited = false
(config: Config) => {
if (inited)
throw new IllegalStateException("AppProperties already initialized")
implicit val credential1 = mgr1 := new FileManager(config)
mgr1 := new FileManager(config) // works
implicit val credential2 = mgr2 := new FileManager(config) // We get a new credential for this one
mgr2 := new FileManager(config) // works
inited = true
}
}
init(new Config)
mgr1 := new FileManager(new Config) // forbidden
这一次,我们完全允许多次分配var,但我们需要在范围内拥有正确的凭据。在第一次分配时创建并返回凭证,这就是我们需要立即将其保存到implicit val credential = mgr := new FileManager(config)
的原因。如果凭证不正确,则无法使用。
(请注意,如果范围中有更多凭据,则隐式凭证不起作用,因为它们具有相同的类型。可能可以解决此问题但我目前还不确定。)
答案 5 :(得分:0)
我在想这样的事情:
object AppProperties {
var p : Int => Unit = { v : Int => p = { _ => throw new IllegalStateException } ; hiddenx = v }
def x_=(v : Int) = p(v)
def x = hiddenx
private var hiddenx = 0
}
X可以设置一次。
答案 6 :(得分:0)
这不完全相同,但在很多情况下,这个'设置变量一次然后继续使用'的解决方案是简单的子类化,有或没有特殊的工厂方法。
abstract class AppPropertyBase {
def mgr: FileManager
}
//.. somewhere else, early in the initialisation
// but of course the assigning scope is no different from the accessing scope
val AppProperties = new AppPropertyBase {
def mgr = makeFileMaker(...)
}
答案 7 :(得分:0)
您始终可以将该值移动到另一个对象,仅初始化一次并在需要时访问它。
object FileManager {
private var fileManager : String = null
def makeManager(initialValue : String ) : String = {
if( fileManager == null ) {
fileManager = initialValue;
}
return fileManager
}
def manager() : String = fileManager
}
object AppProperties {
def init( config : String ) {
val y = FileManager.makeManager( config )
// do something with ...
}
def other() {
FileManager.makeManager( "x" )
FileManager.makeManager( "y" )
val y = FileManager.manager()
// use initilized y
print( y )
// the manager can't be modified
}
}
object Main {
def main( args : Array[String] ) {
AppProperties.init("Hello")
AppProperties.other
}
}