在Scala中键入private[this]
,我们可以使私有成员只能由该类的特定对象访问,并且同一个类的另一个对象无法访问它。如果可能的话,我们怎样才能在其他语言中实现这一点......特别是F#,Erlang和Clojure?
答案 0 :(得分:6)
您所询问的基本上是面向对象的数据抽象与抽象数据类型的数据抽象( ADT s,不要与代数数据类型混淆,代数数据类型通常也缩写为“ADT”。 (On Understanding Data Abstraction, Revisited,William R. Cook和Proposal for Simplified, Modern Definitions of "Object" and "Object Oriented"见Object-Oriented Programming Versus Abstract Data Types。)
相同抽象数据类型的两个不同实例可以检查彼此的表示(但不是其他抽象数据类型的实例的表示),而对象可以< em> never 检查另一个对象的表示,即使该对象是相同类型的实例。 Cook将此属性识别为面向对象数据抽象(实际上是面向对象)的基本和定义特征,并将其称为 Autognosis (自我知识)。我更喜欢 Xenoagnosis (外国非知识)一词,由Glenn Vanderburg,Rick de Natale或已故的Jim Weirich,IIRC应用,因为它强调了两种方法之间的区别,即物体无法了解其他物体。
在Scala中,正如您所注意到的,可以通过private[this]
访问修饰符实现面向对象的数据抽象。
class TestPrivate {
private val notReallyPrivate = 42
private[this] val privateIMeanIt = 23
def blowUp(other: TestPrivate) = {
other.notReallyPrivate
other.privateIMeanIt
}
}
// error: value privateIMeanIt is not a member of TestPrivate
在Erlang中,与Object相当的是一个Process,而Processes完全由语言语义封装(它们甚至有自己的垃圾收集内存)。毕竟,它们甚至可以生活在不同大陆的不同机器上,因此暴露表示只是不切实际。您向流程发送消息,并且流程在决定如何处理消息以及如何响应消息时完全自主。 Everything 基本上都是private[this]
。
在Clojure中,可以通过Closures实现面向对象的数据抽象。 (对于几乎所有带闭包和高阶子程序的语言都是如此,例如你也可以在Erlang中使用函数作为对象而不是进程。)想想ECMAScript:尽管它容易混淆地有一个叫做“object”的语言结构, ECMAScript对象实际上是使用函数实现的。你可以用同样的方式在Clojure中实现它们。面向对象的数据抽象形式也称为过程数据抽象,因为它不依赖于类型,而是依赖于将数据隐藏在过程接口(或功能接口)之后,如果你想要的话,它毕竟是可变性和副作用与OO正交)。 Cook(半开玩笑地)认为,因为λ演算只有函数,所有抽象都是函数式的,因此λ演算是第一个,最古老,最纯粹的OO语言 - 显然这意味着OO数据抽象必须在语言中紧密基于λ演算,如Lisp族。 (历史轶事:Scheme最初是为研究OO和Actors而设计的,他们只是在实现过程中注意到,用于消息发送和函数调用的解释器的代码路径是相同的。)
在Java或C♯等语言中,只使用interface
作为类型,使用Type System和Programmer Discipline实现面向对象。 interface
无法描述表示,因此,只要您确定(通过程序员规则,编码样式,代码评论,可能是静态分析工具),只有interface
被用作类型(每个本地)变量,字段,方法参数,方法返回值,强制转换操作符,instanceof
检查和泛型类型参数必须始终为interface
)和class
es 仅用作工厂(允许出现类名的唯一位置直接位于new
关键字旁边),然后您已实现面向对象的数据抽象。 (还有一些其他注意事项,最重要的是你不能使用参考平等。)
interface ITestPrivate {
default int thisIsPublic() { return 0; }
default void blowUp1(ITestPrivate other) {
other.thisIsPublic();
other.privateIMeanIt();
}
}
class TestPrivate implements ITestPrivate {
private int privateIMeanIt() { return 23; }
void blowUp2(TestPrivate other1, ITestPrivate other2) {
other1.notReallyPrivate(); // works
other2.notReallyPrivate(); // doesn't
}
}
从F♯ supports interface
s as well开始,也可以在F♯中执行此 。
答案 1 :(得分:3)
F#有public
,internal
和private
access control specifiers。 Scalas限制性更强private[this]
限制对类型实例的访问而不是类型本身不能作为说明符使用,因此即使private
(F#中限制最多)成员也是如此可以访问other
实例的。 class let
bindings虽然是实例私有的:
type Foo(x) =
let totallyPrivate = x
member private(*[this]*) __.Bar = x
member this.Baz (other : Foo) =
// other.Bar not accessible in Scala
this.Bar + other.Bar + totallyPrivate
// the field, constructor or member 'totallyPrivate' is not defined
// other.totallyPrivate
答案 2 :(得分:1)
正如我对@JörgWMittag回答的评论所暗示的那样,我倾向于同意@ SamEstep的评论,即你想要做的事,codingXeno,在Clojure中很少有意义。不过,在Common Lisp或Scheme中,这是一个很好的练习,在Clojure中只是稍微难一点。以下是您所要求的简单说明:
(def getit-and-setit (let [x (atom 42)]
[(fn [] @x), (fn [new-x] (reset! x new-x))]))
这里我们定义了一个局部变量x
,其值为“atom”,即保存可以用新值替换的值的东西。 (可以使用@
或deref
提取原子的内容。可以使用reset!
或其他一些有用的运算符替换它。)然后我们返回一个双元素向量。每个元素都是一个函数。 (通常不使用逗号,但允许使用逗号并使此代码更清晰。)
(def get-it (first getit-and-setit))
(def set-it! (second getit-and-setit))
这里提取了第一个函数并定义了一个新变量get-it
以将函数作为其值。我们定义set-it!
将另一个函数作为其值。
get-it
返回原子的内容,即x
的值:
(get-it) ;==> 42
set-it!
用新值替换atom的内容:
(set-it! 5)
(get-it) ;==> 5
除了通过x
和get-it
之外,无法访问set-it!
。 x
是“私人”。
Common Lisp或Scheme中更简单的原因是,在这些语言中,可以为普通局部变量赋予新值。 Clojure通常不允许这样做。 (如果你使用Java或Javascript互操作方法,有很多方法可以在不使用Clojure中的原子的情况下更改变量的值。但是,这是你应该只为了互操作或其他一些特殊需要而做的事情。)
另一方面,如果您想限制对Clojure defrecord
结构的组件的访问,我认为您不能这样做,除非使用上面给出的方法。您可以使用相关的deftype
数据结构来执行此操作,但这通常用于Java互操作。
答案 3 :(得分:0)
请参阅@ JohnPalmer的评论以及Access Control。使用Google可以很容易地找到它。
type TestPrivate(x) =
let y = x - 10
member private __.X() = 10
member __.Z() = x + 10
member __.Y = __.X()
let test = TestPrivate(5)
test.Z() //val it : int = 15
test.Y //val it : int = 10
test.X()
错误FS0491:成员或对象构造函数&#39; X&#39;不可访问。 私人会员只能通过声明类型进行访问。 受保护的成员只能从扩展类型访问 不能从内部lambda表达离子访问。
修改强>
type TestPrivate(x:string) =
let totallyPrivate = x
member private __.PrivateX = x
member __.FromOther(other:TestPrivate) =
__.PrivateX + "-" + other.Y + "-" + other.PrivateX + "-" + totallyPrivate
member __.Y = __.PrivateX
let thisTest = TestPrivate("this")
let otherTest = TestPrivate("other")
thisTest.FromOther(otherTest) //val it : string = "this-other-other-this"
otherTest.FromOther(thisTest) //val it : string = "other-this-this-other"
thisTest.FromOther(thisTest) //val it : string = "this-this-this-this"
otherTest.FromOther(otherTest) //val it : string = "other-other-other-other"
因此,this
实例当然可以访问totalPrivate,但this
或other
都无法访问其他人的绑定,尽管他们可以访问另一个&#39 ; s私有财产。