Reputation: 2866
i needed two instances that has access to each other privates. i naturaly thought of a companion object that grants access to a one and only instance of it's companion class. the class itself i made private, so users cannot just create instances using new
.
object A {
def apply = dual
lazy val dual = new A
}
private class A {
//some irrelevant logic...
}
this code does not compile. i get: class A escapes its defining scope as part of type A error, which i don't really understand. my current workaround was to define a trait with every method declaration the class should have and make class A
extend that trait, while dual is of the trait type, and not class A
type.
what's the theoretic problem i'm missing here? why is this forbiden?
Upvotes: 20
Views: 3826
Reputation: 116306
Paolo's solution is good (+1), but he didn't explain the error message, so let me try that. The problem stems from the fact that every method needs a return type. Your original definition of apply
and dual
returned an object of class A
, thus the implicit return type of both was A
. That implies that A
must be visible to clients - how else could they call the function or access the val
? Moreover, as both - and their parent object too - are public, they are globally visible. However, you declared A private
which means it must not be visible outside its package. So there is a conflict which can't be resolved by the compiler.
The general rule is that all parameter and return type of functions / members must have (at least) the same scope of visibility as the referring member itself*. Thus one trivial way to solve this problem would be to reduce the visibility of apply
and dual
to private
. This would satisfy the compiler, but not you :-)
Your solution gets around the problem by changing the static return type to a public
trait, which thus has the same visibility as the members referring to it. The dynamic type of the returned object is still class A
, however, this need not be visible to clients. This is a classic example of the principle "program to interfaces, not implementations".
Note that to apply this principle to the full extent, one could turn class A
into a private
inner class of object A
, thus making it innaccessible even for other classes within the same package:
trait A {
//...
}
object A {
def apply: A = dual
lazy val dual: A = new AImpl
private class AImpl extends A {
//some irrelevant logic...
}
}
* To be pedantic, the enclosing class / object may reduce the visibility of its members, like here:
private class Holder {
def member = new Hidden
}
private class Hidden
where member
is public
but its enclosing class is private
, effectively hiding its members from the external world. So the compiler emits no complaints here.
Upvotes: 29
Reputation: 25874
I think you don't want a private class, but a class with a private constructor.
class A private()
object A {
def apply = dual
lazy val dual = new A
}
Now your class is "visible" to outside code, but only your companion object can create instances of it.
Upvotes: 25